From 7d7837e5b6eb0fc88d202936b726ab890f4add53 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 8 Mar 2026 19:27:15 -0400 Subject: [PATCH 001/200] disable fallback to free nano for small model --- packages/opencode/src/provider/provider.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index b4836ae047..6d5c9d1ad3 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1288,12 +1288,6 @@ export namespace Provider { } } - // Check if opencode provider is available before using it - const opencodeProvider = await state().then((state) => state.providers["opencode"]) - if (opencodeProvider && opencodeProvider.models["gpt-5-nano"]) { - return getModel("opencode", "gpt-5-nano") - } - return undefined } From b976f339e857be3fae20f179992aa82fbea24f08 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:28:58 -0500 Subject: [PATCH 002/200] feat(app): generate color palettes (#16232) --- .../app/e2e/projects/projects-close.spec.ts | 24 +- packages/app/e2e/settings/settings.spec.ts | 15 +- packages/app/public/oc-theme-preload.js | 7 +- packages/app/src/components/terminal.tsx | 2 +- packages/ui/src/styles/theme.css | 820 +++++++++--------- packages/ui/src/theme/color.ts | 80 +- packages/ui/src/theme/context.tsx | 6 +- packages/ui/src/theme/default-themes.ts | 2 +- .../ui/src/theme/desktop-theme.schema.json | 67 +- packages/ui/src/theme/index.ts | 4 + packages/ui/src/theme/loader.ts | 2 +- packages/ui/src/theme/resolve.ts | 660 +++++++++++--- packages/ui/src/theme/themes/aura.json | 108 +-- packages/ui/src/theme/themes/ayu.json | 110 +-- packages/ui/src/theme/themes/carbonfox.json | 97 +-- packages/ui/src/theme/themes/catppuccin.json | 110 +-- packages/ui/src/theme/themes/dracula.json | 112 +-- packages/ui/src/theme/themes/gruvbox.json | 111 +-- packages/ui/src/theme/themes/monokai.json | 112 +-- packages/ui/src/theme/themes/nightowl.json | 109 +-- packages/ui/src/theme/themes/nord.json | 109 +-- packages/ui/src/theme/themes/oc-1.json | 504 +---------- packages/ui/src/theme/themes/oc-2.json | 495 +---------- packages/ui/src/theme/themes/onedarkpro.json | 110 +-- .../ui/src/theme/themes/shadesofpurple.json | 108 +-- packages/ui/src/theme/themes/solarized.json | 110 +-- packages/ui/src/theme/themes/tokyonight.json | 132 +-- packages/ui/src/theme/themes/vesper.json | 109 +-- packages/ui/src/theme/types.ts | 21 +- 29 files changed, 1268 insertions(+), 2988 deletions(-) diff --git a/packages/app/e2e/projects/projects-close.spec.ts b/packages/app/e2e/projects/projects-close.spec.ts index 76b1487e9e..9454d683f0 100644 --- a/packages/app/e2e/projects/projects-close.spec.ts +++ b/packages/app/e2e/projects/projects-close.spec.ts @@ -25,16 +25,26 @@ test("closing active project navigates to another open project", async ({ page, await clickMenuItem(menu, /^Close$/i, { force: true }) await expect - .poll(() => { - const pathname = new URL(page.url()).pathname - if (new RegExp(`^/${slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project" - if (pathname === "/") return "home" - return "" - }) + .poll( + () => { + const pathname = new URL(page.url()).pathname + if (new RegExp(`^/${slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project" + if (pathname === "/") return "home" + return "" + }, + { timeout: 15_000 }, + ) .toMatch(/^(project|home)$/) await expect(page).not.toHaveURL(new RegExp(`/${otherSlug}/session(?:[/?#]|$)`)) - await expect(otherButton).toHaveCount(0) + await expect + .poll( + async () => { + return await page.locator(projectSwitchSelector(otherSlug)).count() + }, + { timeout: 15_000 }, + ) + .toBe(0) }, { extra: [other] }, ) diff --git a/packages/app/e2e/settings/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts index c2a8522eb0..42fe0b06c9 100644 --- a/packages/app/e2e/settings/settings.spec.ts +++ b/packages/app/e2e/settings/settings.spec.ts @@ -83,16 +83,23 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) => const select = dialog.locator(settingsThemeSelector) await expect(select).toBeVisible() + const currentThemeId = await page.evaluate(() => { + return document.documentElement.getAttribute("data-theme") + }) + const currentTheme = (await select.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? "" + await select.locator('[data-slot="select-select-trigger"]').click() const items = page.locator('[data-slot="select-select-item"]') const count = await items.count() expect(count).toBeGreaterThan(1) - const firstTheme = await items.nth(1).locator('[data-slot="select-select-item-label"]').textContent() - expect(firstTheme).toBeTruthy() + const nextTheme = (await items.locator('[data-slot="select-select-item-label"]').allTextContents()) + .map((x) => x.trim()) + .find((x) => x && x !== currentTheme) + expect(nextTheme).toBeTruthy() - await items.nth(1).click() + await items.filter({ hasText: nextTheme! }).first().click() await page.keyboard.press("Escape") @@ -101,7 +108,7 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) => }) expect(storedThemeId).not.toBeNull() - expect(storedThemeId).not.toBe("oc-1") + expect(storedThemeId).not.toBe(currentThemeId) const dataTheme = await page.evaluate(() => { return document.documentElement.getAttribute("data-theme") diff --git a/packages/app/public/oc-theme-preload.js b/packages/app/public/oc-theme-preload.js index f8c7104961..5851f756e5 100644 --- a/packages/app/public/oc-theme-preload.js +++ b/packages/app/public/oc-theme-preload.js @@ -1,6 +1,5 @@ ;(function () { - var themeId = localStorage.getItem("opencode-theme-id") - if (!themeId) return + var themeId = localStorage.getItem("opencode-theme-id") || "oc-2" var scheme = localStorage.getItem("opencode-color-scheme") || "system" var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches) @@ -9,9 +8,9 @@ document.documentElement.dataset.theme = themeId document.documentElement.dataset.colorScheme = mode - if (themeId === "oc-1") return + if (themeId === "oc-2") return - var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode) + var css = localStorage.getItem("opencode-theme-css-" + mode) if (css) { var style = document.createElement("style") style.id = "oc-theme-preload" diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index c27d6a977f..9e5f12ee4c 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -217,7 +217,7 @@ export const Terminal = (props: TerminalProps) => { const currentTheme = theme.themes()[theme.themeId()] if (!currentTheme) return fallback const variant = mode === "dark" ? currentTheme.dark : currentTheme.light - if (!variant?.seeds) return fallback + if (!variant?.seeds && !variant?.palette) return fallback const resolved = resolveThemeVariant(variant, mode === "dark") const text = resolved["text-stronger"] ?? fallback.foreground const background = resolved["background-stronger"] ?? fallback.background diff --git a/packages/ui/src/styles/theme.css b/packages/ui/src/styles/theme.css index 2637b4a281..702d1e4e67 100644 --- a/packages/ui/src/styles/theme.css +++ b/packages/ui/src/styles/theme.css @@ -85,214 +85,218 @@ 0 0 0 1px var(--border-weak-base, rgba(0, 0, 0, 0.07)), 0 36px 80px 0 rgba(0, 0, 0, 0.03), 0 13.141px 29.201px 0 rgba(0, 0, 0, 0.04), 0 6.38px 14.177px 0 rgba(0, 0, 0, 0.05), 0 3.127px 6.95px 0 rgba(0, 0, 0, 0.06), 0 1.237px 2.748px 0 rgba(0, 0, 0, 0.09); - --shadow-sidebar-overlay: - 0 100px 80px 0 rgba(0, 0, 0, 0.29), 0 41.778px 33.422px 0 rgba(0, 0, 0, 0.21), - 0 22.336px 17.869px 0 rgba(0, 0, 0, 0.17), 0 12.522px 10.017px 0 rgba(0, 0, 0, 0.14), - 0 6.65px 5.32px 0 rgba(0, 0, 0, 0.12), 0 2.767px 2.214px 0 rgba(0, 0, 0, 0.08); color-scheme: light; --text-mix-blend-mode: multiply; - /* OC-1 fallback variables (light) */ - --background-base: #f8f7f7; - --background-weak: var(--smoke-light-3); - --background-strong: var(--smoke-light-1); + /* OC-2 fallback variables (light) */ + --background-base: #f8f8f8; + --background-weak: #f3f3f3; + --background-strong: #fcfcfc; --background-stronger: #fcfcfc; - --surface-base: var(--smoke-light-alpha-2); - --base: var(--smoke-light-alpha-2); - --surface-base-hover: #0500000f; - --surface-base-active: var(--smoke-light-alpha-3); - --surface-base-interactive-active: var(--cobalt-light-alpha-3); - --base2: var(--smoke-light-alpha-2); - --base3: var(--smoke-light-alpha-2); - --surface-inset-base: var(--smoke-light-alpha-2); - --surface-inset-base-hover: var(--smoke-light-alpha-3); - --surface-inset-strong: #1f000017; - --surface-inset-strong-hover: #1f000017; - --surface-raised-base: var(--smoke-light-alpha-1); - --surface-float-base: var(--smoke-dark-1); - --surface-float-base-hover: var(--smoke-dark-2); - --surface-raised-base-hover: var(--smoke-light-alpha-2); - --surface-raised-base-active: var(--smoke-light-alpha-3); - --surface-raised-strong: var(--smoke-light-1); - --surface-raised-strong-hover: var(--white); - --surface-raised-stronger: var(--white); - --surface-raised-stronger-hover: var(--white); - --surface-weak: var(--smoke-light-alpha-3); - --surface-weaker: var(--smoke-light-alpha-4); + --surface-base: rgba(0, 0, 0, 0.031); + --base: rgba(0, 0, 0, 0.034); + --surface-base-hover: rgba(0, 0, 0, 0.059); + --surface-base-active: rgba(0, 0, 0, 0.051); + --surface-base-interactive-active: rgba(3, 76, 255, 0.09); + --base2: rgba(0, 0, 0, 0.034); + --base3: rgba(0, 0, 0, 0.034); + --surface-inset-base: rgba(0, 0, 0, 0.034); + --surface-inset-base-hover: rgba(0, 0, 0, 0.055); + --surface-inset-strong: rgba(0, 0, 0, 0.09); + --surface-inset-strong-hover: rgba(0, 0, 0, 0.09); + --surface-raised-base: rgba(0, 0, 0, 0.031); + --surface-float-base: #161616; + --surface-float-base-hover: #1c1c1c; + --surface-raised-base-hover: rgba(0, 0, 0, 0.051); + --surface-raised-base-active: rgba(0, 0, 0, 0.09); + --surface-raised-strong: #fcfcfc; + --surface-raised-strong-hover: #ffffff; + --surface-raised-stronger: #ffffff; + --surface-raised-stronger-hover: #ffffff; + --surface-weak: rgba(0, 0, 0, 0.051); + --surface-weaker: rgba(0, 0, 0, 0.071); --surface-strong: #ffffff; --surface-stronger-non-alpha: var(--surface-raised-stronger-non-alpha); - --surface-raised-stronger-non-alpha: var(--white); - --surface-brand-base: var(--yuzu-light-9); - --surface-brand-hover: var(--yuzu-light-10); - --surface-interactive-base: var(--cobalt-light-3); - --surface-interactive-hover: #e5f0ff; - --surface-interactive-weak: var(--cobalt-light-2); - --surface-interactive-weak-hover: var(--cobalt-light-3); - --surface-success-base: var(--apple-light-3); - --surface-success-weak: var(--apple-light-2); - --surface-success-strong: var(--apple-light-9); - --surface-warning-base: var(--solaris-light-3); - --surface-warning-weak: var(--solaris-light-2); - --surface-warning-strong: var(--solaris-light-9); - --surface-critical-base: var(--ember-light-3); - --surface-critical-weak: var(--ember-light-2); - --surface-critical-strong: var(--ember-light-9); - --surface-info-base: var(--lilac-light-3); - --surface-info-weak: var(--lilac-light-2); - --surface-info-strong: var(--lilac-light-9); + --surface-raised-stronger-non-alpha: #ffffff; + --surface-brand-base: #dcde8d; + --surface-brand-hover: #d0d283; + --surface-interactive-base: #ecf3ff; + --surface-interactive-hover: #e0eaff; + --surface-interactive-weak: #f7faff; + --surface-interactive-weak-hover: #ecf3ff; + --surface-success-base: #dbfed7; + --surface-success-weak: #f0feee; + --surface-success-strong: #12c905; + --surface-warning-base: #fcf3cb; + --surface-warning-weak: #fdfaec; + --surface-warning-strong: #fbdd46; + --surface-critical-base: #feefeb; + --surface-critical-weak: #fff8f6; + --surface-critical-strong: #fc533a; + --surface-info-base: #fdecfe; + --surface-info-weak: #fef7ff; + --surface-info-strong: #a753ae; --surface-diff-unchanged-base: #ffffff00; - --surface-diff-skip-base: var(--smoke-light-2); - --surface-diff-hidden-base: var(--blue-light-3); - --surface-diff-hidden-weak: var(--blue-light-2); - --surface-diff-hidden-weaker: var(--blue-light-1); - --surface-diff-hidden-strong: var(--blue-light-5); - --surface-diff-hidden-stronger: var(--blue-light-9); - --surface-diff-add-base: #dafbe0; - --surface-diff-add-weak: var(--mint-light-2); - --surface-diff-add-weaker: var(--mint-light-1); - --surface-diff-add-strong: var(--mint-light-5); - --surface-diff-add-stronger: var(--mint-light-9); - --surface-diff-delete-base: var(--ember-light-3); - --surface-diff-delete-weak: var(--ember-light-2); - --surface-diff-delete-weaker: var(--ember-light-1); - --surface-diff-delete-strong: var(--ember-light-6); - --surface-diff-delete-stronger: var(--ember-light-9); - --input-base: var(--smoke-light-1); - --input-hover: var(--smoke-light-2); - --input-active: var(--cobalt-light-1); - --input-selected: var(--cobalt-light-4); - --input-focus: var(--cobalt-light-1); - --input-disabled: var(--smoke-light-4); - --text-base: var(--smoke-light-11); - --text-weak: var(--smoke-light-9); - --text-weaker: var(--smoke-light-8); - --text-strong: var(--smoke-light-12); - --text-invert-base: var(--smoke-dark-alpha-11); - --text-invert-weak: var(--smoke-dark-alpha-9); - --text-invert-weaker: var(--smoke-dark-alpha-8); - --text-invert-strong: var(--smoke-dark-alpha-12); - --text-interactive-base: var(--cobalt-light-9); - --text-on-brand-base: var(--smoke-light-alpha-11); - --text-on-interactive-base: var(--smoke-light-1); - --text-on-interactive-weak: var(--smoke-dark-alpha-11); - --text-on-success-base: var(--apple-light-10); - --text-on-critical-base: var(--ember-light-10); - --text-on-critical-weak: var(--ember-light-8); - --text-on-critical-strong: var(--ember-light-12); - --text-on-warning-base: var(--smoke-dark-alpha-11); - --text-on-info-base: var(--smoke-dark-alpha-11); - --text-diff-add-base: var(--mint-light-11); - --text-diff-delete-base: var(--ember-light-10); - --text-diff-delete-strong: var(--ember-light-12); - --text-diff-add-strong: var(--mint-light-12); - --text-on-info-weak: var(--smoke-dark-alpha-9); - --text-on-info-strong: var(--smoke-dark-alpha-12); - --text-on-warning-weak: var(--smoke-dark-alpha-9); - --text-on-warning-strong: var(--smoke-dark-alpha-12); - --text-on-success-weak: var(--apple-light-6); - --text-on-success-strong: var(--apple-light-12); - --text-on-brand-weak: var(--smoke-light-alpha-9); - --text-on-brand-weaker: var(--smoke-light-alpha-8); - --text-on-brand-strong: var(--smoke-light-alpha-12); - --button-primary-base: var(--smoke-light-12); - --button-secondary-base: #fdfcfc; - --button-secondary-hover: #faf9f9; - --border-base: var(--smoke-light-alpha-7); - --border-hover: var(--smoke-light-alpha-8); - --border-active: var(--smoke-light-alpha-9); - --border-selected: var(--cobalt-light-alpha-9); - --border-disabled: var(--smoke-light-alpha-8); - --border-focus: var(--smoke-light-alpha-9); - --border-weak-base: var(--smoke-light-alpha-5); - --border-strong-base: var(--smoke-light-alpha-7); - --border-strong-hover: var(--smoke-light-alpha-8); - --border-strong-active: var(--smoke-light-alpha-7); - --border-strong-selected: var(--cobalt-light-alpha-6); - --border-strong-disabled: var(--smoke-light-alpha-6); - --border-strong-focus: var(--smoke-light-alpha-7); - --border-weak-hover: var(--smoke-light-alpha-6); - --border-weak-active: var(--smoke-light-alpha-7); - --border-weak-selected: var(--cobalt-light-alpha-5); - --border-weak-disabled: var(--smoke-light-alpha-6); - --border-weak-focus: var(--smoke-light-alpha-7); - --border-weaker-base: var(--smoke-light-alpha-3); - --border-interactive-base: var(--cobalt-light-7); - --border-interactive-hover: var(--cobalt-light-8); - --border-interactive-active: var(--cobalt-light-9); - --border-interactive-selected: var(--cobalt-light-9); - --border-interactive-disabled: var(--smoke-light-8); - --border-interactive-focus: var(--cobalt-light-9); - --border-success-base: var(--apple-light-6); - --border-success-hover: var(--apple-light-7); - --border-success-selected: var(--apple-light-9); - --border-warning-base: var(--solaris-light-6); - --border-warning-hover: var(--solaris-light-7); - --border-warning-selected: var(--solaris-light-9); - --border-critical-base: var(--ember-light-6); - --border-critical-hover: var(--ember-light-7); - --border-critical-selected: var(--ember-light-9); - --border-info-base: var(--lilac-light-6); - --border-info-hover: var(--lilac-light-7); - --border-info-selected: var(--lilac-light-9); - --icon-base: var(--smoke-light-9); - --icon-hover: var(--smoke-light-11); - --icon-active: var(--smoke-light-12); - --icon-selected: var(--smoke-light-12); - --icon-disabled: var(--smoke-light-8); - --icon-focus: var(--smoke-light-12); + --surface-diff-skip-base: #f8f8f8; + --surface-diff-hidden-base: #eaf4ff; + --surface-diff-hidden-weak: #f6faff; + --surface-diff-hidden-weaker: #fbfdff; + --surface-diff-hidden-strong: #cae3ff; + --surface-diff-hidden-stronger: #2090f5; + --surface-diff-add-base: #e3fae1; + --surface-diff-add-weak: #f4fcf3; + --surface-diff-add-weaker: #fbfefb; + --surface-diff-add-strong: #c2eebf; + --surface-diff-add-stronger: #9ff29a; + --surface-diff-delete-base: #feefeb; + --surface-diff-delete-weak: #fff8f6; + --surface-diff-delete-weaker: #fffcfb; + --surface-diff-delete-strong: #fdc3b7; + --surface-diff-delete-stronger: #fc533a; + --input-base: #fcfcfc; + --input-hover: #f8f8f8; + --input-active: #fcfdff; + --input-selected: #e0eaff; + --input-focus: #fcfdff; + --input-disabled: #ededed; + --text-base: #6f6f6f; + --text-weak: #8f8f8f; + --text-weaker: #c7c7c7; + --text-strong: #171717; + --text-invert-base: #f8f8f8; + --text-invert-weak: #f3f3f3; + --text-invert-weaker: #ededed; + --text-invert-strong: #fcfcfc; + --text-interactive-base: #034cff; + --text-on-brand-base: rgba(0, 0, 0, 0.574); + --text-on-interactive-base: #fcfcfc; + --text-on-interactive-weak: rgba(0, 0, 0, 0.574); + --text-on-success-base: #2dba26; + --text-on-critical-base: #ed4831; + --text-on-critical-weak: #fe806a; + --text-on-critical-strong: #601a0f; + --text-on-warning-base: rgba(0, 0, 0, 0.574); + --text-on-info-base: rgba(0, 0, 0, 0.574); + --text-diff-add-base: #3a8437; + --text-diff-delete-base: #ed4831; + --text-diff-delete-strong: #601a0f; + --text-diff-add-strong: #1d3e1c; + --text-on-info-weak: rgba(0, 0, 0, 0.453); + --text-on-info-strong: rgba(0, 0, 0, 0.915); + --text-on-warning-weak: rgba(0, 0, 0, 0.453); + --text-on-warning-strong: rgba(0, 0, 0, 0.915); + --text-on-success-weak: #96ec8e; + --text-on-success-strong: #044202; + --text-on-brand-weak: rgba(0, 0, 0, 0.453); + --text-on-brand-weaker: rgba(0, 0, 0, 0.232); + --text-on-brand-strong: rgba(0, 0, 0, 0.915); + --button-primary-base: #171717; + --button-secondary-base: #fcfcfc; + --button-secondary-hover: #f8f8f8; + --button-ghost-hover: rgba(0, 0, 0, 0.031); + --button-ghost-hover2: rgba(0, 0, 0, 0.051); + --border-base: rgba(0, 0, 0, 0.162); + --border-hover: rgba(0, 0, 0, 0.236); + --border-active: rgba(0, 0, 0, 0.46); + --border-selected: rgba(3, 76, 255, 0.99); + --border-disabled: rgba(0, 0, 0, 0.236); + --border-focus: rgba(0, 0, 0, 0.46); + --border-weak-base: #e5e5e5; + --border-strong-base: rgba(0, 0, 0, 0.151); + --border-strong-hover: rgba(0, 0, 0, 0.232); + --border-strong-active: rgba(0, 0, 0, 0.151); + --border-strong-selected: rgba(3, 76, 255, 0.31); + --border-strong-disabled: rgba(0, 0, 0, 0.118); + --border-strong-focus: rgba(0, 0, 0, 0.151); + --border-weak-hover: rgba(0, 0, 0, 0.118); + --border-weak-active: rgba(0, 0, 0, 0.151); + --border-weak-selected: rgba(3, 76, 255, 0.24); + --border-weak-disabled: rgba(0, 0, 0, 0.118); + --border-weak-focus: rgba(0, 0, 0, 0.151); + --border-weaker-base: #f0f0f0; + --border-weaker-hover: rgba(0, 0, 0, 0.075); + --border-weaker-active: rgba(0, 0, 0, 0.118); + --border-weaker-selected: rgba(3, 76, 255, 0.16); + --border-weaker-disabled: rgba(0, 0, 0, 0.034); + --border-weaker-focus: rgba(0, 0, 0, 0.118); + --border-interactive-base: #a3c1fd; + --border-interactive-hover: #7ea9ff; + --border-interactive-active: #034cff; + --border-interactive-selected: #034cff; + --border-interactive-disabled: #c7c7c7; + --border-interactive-focus: #034cff; + --border-success-base: #96ec8e; + --border-success-hover: #7add71; + --border-success-selected: #12c905; + --border-warning-base: #e8d479; + --border-warning-hover: #d8c158; + --border-warning-selected: #fbdd46; + --border-critical-base: #fdc3b7; + --border-critical-hover: #ffa796; + --border-critical-selected: #fc533a; + --border-info-base: #f4bdf8; + --border-info-hover: #e6a8ea; + --border-info-selected: #a753ae; + --border-color: #ffffff; + --icon-base: #8f8f8f; + --icon-hover: #6f6f6f; + --icon-active: #171717; + --icon-selected: #171717; + --icon-disabled: #c7c7c7; + --icon-focus: #171717; --icon-invert-base: #ffffff; - --icon-weak-base: var(--smoke-light-7); - --icon-weak-hover: var(--smoke-light-8); - --icon-weak-active: var(--smoke-light-9); - --icon-weak-selected: var(--smoke-light-10); - --icon-weak-disabled: var(--smoke-light-6); - --icon-weak-focus: var(--smoke-light-9); - --icon-strong-base: var(--smoke-light-12); + --icon-weak-base: #dbdbdb; + --icon-weak-hover: #c7c7c7; + --icon-weak-active: #8f8f8f; + --icon-weak-selected: #858585; + --icon-weak-disabled: #e2e2e2; + --icon-weak-focus: #8f8f8f; + --icon-strong-base: #171717; --icon-strong-hover: #151313; --icon-strong-active: #020202; --icon-strong-selected: #020202; - --icon-strong-disabled: var(--smoke-light-8); + --icon-strong-disabled: #c7c7c7; --icon-strong-focus: #020202; - --icon-brand-base: var(--smoke-light-12); - --icon-interactive-base: var(--cobalt-light-9); - --icon-success-base: var(--apple-light-7); - --icon-success-hover: var(--apple-light-8); - --icon-success-active: var(--apple-light-11); - --icon-warning-base: var(--amber-light-9); - --icon-warning-hover: var(--amber-light-8); - --icon-warning-active: var(--amber-light-11); - --icon-critical-base: var(--ember-light-10); - --icon-critical-hover: var(--ember-light-11); - --icon-critical-active: var(--ember-light-12); - --icon-info-base: var(--lilac-light-7); - --icon-info-hover: var(--lilac-light-8); - --icon-info-active: var(--lilac-light-11); - --icon-on-brand-base: var(--smoke-light-alpha-11); - --icon-on-brand-hover: var(--smoke-light-alpha-12); - --icon-on-brand-selected: var(--smoke-light-alpha-12); - --icon-on-interactive-base: var(--smoke-light-1); - --icon-agent-plan-base: var(--purple-light-9); - --icon-agent-docs-base: var(--amber-light-9); - --icon-agent-ask-base: var(--cyan-light-9); - --icon-agent-build-base: var(--cobalt-light-9); - --icon-on-success-base: var(--apple-light-alpha-9); - --icon-on-success-hover: var(--apple-light-alpha-10); - --icon-on-success-selected: var(--apple-light-alpha-11); - --icon-on-warning-base: var(--amber-lightalpha-9); - --icon-on-warning-hover: var(--amber-lightalpha-10); - --icon-on-warning-selected: var(--amber-lightalpha-11); - --icon-on-critical-base: var(--ember-light-alpha-9); - --icon-on-critical-hover: var(--ember-light-alpha-10); - --icon-on-critical-selected: var(--ember-light-alpha-11); - --icon-on-info-base: var(--lilac-light-9); - --icon-on-info-hover: var(--lilac-light-alpha-10); - --icon-on-info-selected: var(--lilac-light-alpha-11); - --icon-diff-add-base: var(--mint-light-11); - --icon-diff-add-hover: var(--mint-light-12); - --icon-diff-add-active: var(--mint-light-12); - --icon-diff-delete-base: var(--ember-light-10); - --icon-diff-delete-hover: var(--ember-light-11); + --icon-brand-base: #171717; + --icon-interactive-base: #034cff; + --icon-success-base: #7add71; + --icon-success-hover: #4cc944; + --icon-success-active: #078901; + --icon-warning-base: #ebb76e; + --icon-warning-hover: #da9e40; + --icon-warning-active: #95671b; + --icon-critical-base: #ed4831; + --icon-critical-hover: #ca2d17; + --icon-critical-active: #601a0f; + --icon-info-base: #e6a8ea; + --icon-info-hover: #d58cda; + --icon-info-active: #9b4da1; + --icon-on-brand-base: rgba(0, 0, 0, 0.574); + --icon-on-brand-hover: rgba(0, 0, 0, 0.915); + --icon-on-brand-selected: rgba(0, 0, 0, 0.915); + --icon-on-interactive-base: #fcfcfc; + --icon-agent-plan-base: #a753ae; + --icon-agent-docs-base: #fcb239; + --icon-agent-ask-base: #2090f5; + --icon-agent-build-base: #034cff; + --icon-on-success-base: rgba(18, 201, 5, 0.9); + --icon-on-success-hover: rgba(45, 186, 38, 0.9); + --icon-on-success-selected: rgba(7, 137, 1, 0.9); + --icon-on-warning-base: rgba(252, 178, 57, 0.9); + --icon-on-warning-hover: rgba(239, 167, 46, 0.9); + --icon-on-warning-selected: rgba(149, 103, 27, 0.9); + --icon-on-critical-base: rgba(252, 83, 58, 0.9); + --icon-on-critical-hover: rgba(237, 72, 49, 0.9); + --icon-on-critical-selected: rgba(202, 45, 23, 0.9); + --icon-on-info-base: #a753ae; + --icon-on-info-hover: rgba(155, 73, 162, 0.9); + --icon-on-info-selected: rgba(155, 77, 161, 0.9); + --icon-diff-add-base: #3a8437; + --icon-diff-add-hover: #1d3e1c; + --icon-diff-add-active: #1d3e1c; + --icon-diff-delete-base: #ed4831; + --icon-diff-delete-hover: #ca2d17; --icon-diff-modified-base: #ff8c00; --syntax-comment: var(--text-weak); --syntax-regexp: var(--text-base); @@ -306,12 +310,12 @@ --syntax-constant: #007b80; --syntax-punctuation: var(--text-base); --syntax-object: var(--text-strong); - --syntax-success: var(--apple-light-10); - --syntax-warning: var(--amber-light-10); - --syntax-critical: var(--ember-light-10); + --syntax-success: #2dba26; + --syntax-warning: #efa72e; + --syntax-critical: #ed4831; --syntax-info: #0092a8; - --syntax-diff-add: var(--mint-light-11); - --syntax-diff-delete: var(--ember-light-11); + --syntax-diff-add: #3a8437; + --syntax-diff-delete: #ca2d17; --syntax-diff-unknown: #ff0000; --markdown-heading: #d68c27; --markdown-text: #1a1a1a; @@ -327,9 +331,6 @@ --markdown-image: #3b7dd8; --markdown-image-text: #318795; --markdown-code-block: #1a1a1a; - --border-color: #ffffff; - --button-ghost-hover: var(--smoke-light-alpha-2); - --button-ghost-hover2: var(--smoke-light-alpha-3); --avatar-background-pink: #feeef8; --avatar-background-mint: #e1fbf4; --avatar-background-orange: #fff1e7; @@ -342,210 +343,220 @@ --avatar-text-purple: #8445bc; --avatar-text-cyan: #0894b3; --avatar-text-lime: #5d770d; + --text-stronger: #171717; @media (prefers-color-scheme: dark) { color-scheme: dark; --text-mix-blend-mode: plus-lighter; - /* OC-1 fallback variables (dark) */ - --background-base: var(--smoke-dark-1); - --background-weak: #1c1717; - --background-strong: #151313; - --background-stronger: #191515; - --surface-base: var(--smoke-dark-alpha-2); - --base: var(--smoke-dark-alpha-2); - --surface-base-hover: #e0b7b716; - --surface-base-active: var(--smoke-dark-alpha-3); - --surface-base-interactive-active: var(--cobalt-dark-alpha-2); - --base2: var(--smoke-dark-alpha-2); - --base3: var(--smoke-dark-alpha-2); - --surface-inset-base: #0e0b0b7f; - --surface-inset-base-hover: #0e0b0b7f; - --surface-inset-strong: #060505cc; - --surface-inset-strong-hover: #060505cc; - --surface-raised-base: var(--smoke-dark-alpha-3); - --surface-float-base: var(--smoke-dark-1); - --surface-float-base-hover: var(--smoke-dark-2); - --surface-raised-base-hover: var(--smoke-dark-alpha-4); - --surface-raised-base-active: var(--smoke-dark-alpha-5); - --surface-raised-strong: var(--smoke-dark-alpha-4); - --surface-raised-strong-hover: var(--smoke-dark-alpha-6); - --surface-raised-stronger: var(--smoke-dark-alpha-6); - --surface-raised-stronger-hover: var(--smoke-dark-alpha-7); - --surface-weak: var(--smoke-dark-alpha-4); - --surface-weaker: var(--smoke-dark-alpha-5); - --surface-strong: var(--smoke-dark-alpha-7); + /* OC-2 fallback variables (dark) */ + --background-base: #101010; + --background-weak: #1e1e1e; + --background-strong: #121212; + --background-stronger: #151515; + --surface-base: rgba(255, 255, 255, 0.031); + --base: rgba(255, 255, 255, 0.034); + --surface-base-hover: rgba(255, 255, 255, 0.039); + --surface-base-active: rgba(255, 255, 255, 0.059); + --surface-base-interactive-active: rgba(3, 76, 255, 0.125); + --base2: rgba(255, 255, 255, 0.034); + --base3: rgba(255, 255, 255, 0.034); + --surface-inset-base: rgba(0, 0, 0, 0.5); + --surface-inset-base-hover: rgba(0, 0, 0, 0.5); + --surface-inset-strong: rgba(0, 0, 0, 0.8); + --surface-inset-strong-hover: rgba(0, 0, 0, 0.8); + --surface-raised-base: rgba(255, 255, 255, 0.059); + --surface-float-base: #161616; + --surface-float-base-hover: #1c1c1c; + --surface-raised-base-hover: rgba(255, 255, 255, 0.078); + --surface-raised-base-active: rgba(255, 255, 255, 0.102); + --surface-raised-strong: rgba(255, 255, 255, 0.078); + --surface-raised-strong-hover: rgba(255, 255, 255, 0.129); + --surface-raised-stronger: rgba(255, 255, 255, 0.129); + --surface-raised-stronger-hover: rgba(255, 255, 255, 0.169); + --surface-weak: rgba(255, 255, 255, 0.078); + --surface-weaker: rgba(255, 255, 255, 0.102); + --surface-strong: rgba(255, 255, 255, 0.169); --surface-stronger-non-alpha: var(--surface-raised-stronger-non-alpha); - --surface-raised-stronger-non-alpha: var(--smoke-dark-3); - --surface-brand-base: var(--yuzu-light-9); - --surface-brand-hover: var(--yuzu-light-10); - --surface-interactive-base: var(--cobalt-dark-3); - --surface-interactive-hover: #0a1d4d; - --surface-interactive-weak: var(--cobalt-dark-2); - --surface-interactive-weak-hover: var(--cobalt-light-3); - --surface-success-base: var(--apple-light-3); - --surface-success-weak: var(--apple-light-2); - --surface-success-strong: var(--apple-light-9); - --surface-warning-base: var(--solaris-light-3); - --surface-warning-weak: var(--solaris-light-2); - --surface-warning-strong: var(--solaris-light-9); - --surface-critical-base: var(--ember-dark-3); - --surface-critical-weak: var(--ember-dark-2); - --surface-critical-strong: var(--ember-dark-9); - --surface-info-base: var(--lilac-light-3); - --surface-info-weak: var(--lilac-light-2); - --surface-info-strong: var(--lilac-light-9); - --surface-diff-unchanged-base: var(--smoke-dark-1); - --surface-diff-skip-base: var(--smoke-dark-alpha-1); - --surface-diff-hidden-base: var(--blue-dark-2); - --surface-diff-hidden-weak: var(--blue-dark-1); - --surface-diff-hidden-weaker: var(--blue-dark-3); - --surface-diff-hidden-strong: var(--blue-dark-5); - --surface-diff-hidden-stronger: var(--blue-dark-11); - --surface-diff-add-base: var(--mint-dark-3); - --surface-diff-add-weak: var(--mint-dark-4); - --surface-diff-add-weaker: var(--mint-dark-3); - --surface-diff-add-strong: var(--mint-dark-5); - --surface-diff-add-stronger: var(--mint-dark-11); - --surface-diff-delete-base: var(--ember-dark-3); - --surface-diff-delete-weak: var(--ember-dark-4); - --surface-diff-delete-weaker: var(--ember-dark-3); - --surface-diff-delete-strong: var(--ember-dark-5); - --surface-diff-delete-stronger: var(--ember-dark-11); - --input-base: var(--smoke-dark-2); - --input-hover: var(--smoke-dark-2); - --input-active: var(--cobalt-dark-1); - --input-selected: var(--cobalt-dark-2); - --input-focus: var(--cobalt-dark-1); - --input-disabled: var(--smoke-dark-4); - --text-base: var(--smoke-dark-alpha-11); - --text-weak: var(--smoke-dark-alpha-9); - --text-weaker: var(--smoke-dark-alpha-8); - --text-strong: var(--smoke-dark-alpha-12); - --text-invert-base: var(--smoke-dark-alpha-11); - --text-invert-weak: var(--smoke-dark-alpha-9); - --text-invert-weaker: var(--smoke-dark-alpha-8); - --text-invert-strong: var(--smoke-dark-alpha-12); - --text-interactive-base: var(--cobalt-dark-11); - --text-on-brand-base: var(--smoke-dark-alpha-11); - --text-on-interactive-base: var(--smoke-dark-12); - --text-on-interactive-weak: var(--smoke-dark-alpha-11); - --text-on-success-base: var(--apple-dark-9); - --text-on-critical-base: var(--ember-dark-9); - --text-on-critical-weak: var(--ember-dark-8); - --text-on-critical-strong: var(--ember-dark-12); - --text-on-warning-base: var(--smoke-dark-alpha-11); - --text-on-info-base: var(--smoke-dark-alpha-11); - --text-diff-add-base: var(--mint-dark-11); - --text-diff-delete-base: var(--ember-dark-9); - --text-diff-delete-strong: var(--ember-dark-12); - --text-diff-add-strong: var(--mint-dark-8); - --text-on-info-weak: var(--smoke-dark-alpha-9); - --text-on-info-strong: var(--smoke-dark-alpha-12); - --text-on-warning-weak: var(--smoke-dark-alpha-9); - --text-on-warning-strong: var(--smoke-dark-alpha-12); - --text-on-success-weak: var(--apple-dark-8); - --text-on-success-strong: var(--apple-dark-12); - --text-on-brand-weak: var(--smoke-dark-alpha-9); - --text-on-brand-weaker: var(--smoke-dark-alpha-8); - --text-on-brand-strong: var(--smoke-dark-alpha-12); - --button-primary-base: var(--smoke-dark-12); - --button-secondary-base: #231f1f; - --button-secondary-hover: #2a2727; - --border-base: var(--smoke-dark-alpha-7); - --border-hover: var(--smoke-dark-alpha-8); - --border-active: var(--smoke-dark-alpha-9); - --border-selected: var(--cobalt-dark-alpha-11); - --border-disabled: var(--smoke-dark-alpha-8); - --border-focus: var(--smoke-dark-alpha-9); - --border-weak-base: var(--smoke-dark-alpha-6); - --border-strong-base: var(--smoke-dark-alpha-8); - --border-strong-hover: var(--smoke-dark-alpha-7); - --border-strong-active: var(--smoke-dark-alpha-8); - --border-strong-selected: var(--cobalt-dark-alpha-6); - --border-strong-disabled: var(--smoke-dark-alpha-6); - --border-strong-focus: var(--smoke-dark-alpha-8); - --border-weak-hover: var(--smoke-dark-alpha-7); - --border-weak-active: var(--smoke-dark-alpha-8); - --border-weak-selected: var(--cobalt-dark-alpha-6); - --border-weak-disabled: var(--smoke-dark-alpha-6); - --border-weak-focus: var(--smoke-dark-alpha-8); - --border-interactive-base: var(--cobalt-light-7); - --border-interactive-hover: var(--cobalt-light-8); - --border-interactive-active: var(--cobalt-light-9); - --border-interactive-selected: var(--cobalt-light-9); - --border-interactive-disabled: var(--smoke-light-8); - --border-interactive-focus: var(--cobalt-light-9); - --border-success-base: var(--apple-light-6); - --border-success-hover: var(--apple-light-7); - --border-success-selected: var(--apple-light-9); - --border-warning-base: var(--solaris-light-6); - --border-warning-hover: var(--solaris-light-7); - --border-warning-selected: var(--solaris-light-9); - --border-critical-base: var(--ember-dark-5); - --border-critical-hover: var(--ember-dark-7); - --border-critical-selected: var(--ember-dark-9); - --border-info-base: var(--lilac-light-6); - --border-info-hover: var(--lilac-light-7); - --border-info-selected: var(--lilac-light-9); - --icon-base: var(--smoke-dark-9); - --icon-hover: var(--smoke-dark-10); - --icon-active: var(--smoke-dark-11); - --icon-selected: var(--smoke-dark-12); - --icon-disabled: var(--smoke-dark-7); - --icon-focus: var(--smoke-dark-12); - --icon-invert-base: var(--smoke-dark-1); - --icon-weak-base: var(--smoke-dark-6); - --icon-weak-hover: var(--smoke-light-7); - --icon-weak-active: var(--smoke-light-8); - --icon-weak-selected: var(--smoke-light-9); - --icon-weak-disabled: var(--smoke-light-4); - --icon-weak-focus: var(--smoke-light-9); - --icon-strong-base: var(--smoke-dark-12); + --surface-raised-stronger-non-alpha: #1c1c1c; + --surface-brand-base: #fab283; + --surface-brand-hover: #eda779; + --surface-interactive-base: #091f52; + --surface-interactive-hover: #091f52; + --surface-interactive-weak: #0b1730; + --surface-interactive-weak-hover: #ecf3ff; + --surface-success-base: #062d04; + --surface-success-weak: #0a1e08; + --surface-success-strong: #12c905; + --surface-warning-base: #fdf3cf; + --surface-warning-weak: #fdfaed; + --surface-warning-strong: #fcd53a; + --surface-critical-base: #42120b; + --surface-critical-weak: #28110c; + --surface-critical-strong: #fc533a; + --surface-info-base: #feecfe; + --surface-info-weak: #fdf7fe; + --surface-info-strong: #edb2f1; + --surface-diff-unchanged-base: #161616; + --surface-diff-skip-base: #00000000; + --surface-diff-hidden-base: #0c1928; + --surface-diff-hidden-weak: #09131d; + --surface-diff-hidden-weaker: #082542; + --surface-diff-hidden-strong: #073966; + --surface-diff-hidden-stronger: #8ec2fc; + --surface-diff-add-base: #1a2919; + --surface-diff-add-weak: #1f351e; + --surface-diff-add-weaker: #1a2919; + --surface-diff-add-strong: #264024; + --surface-diff-add-stronger: #9bcd97; + --surface-diff-delete-base: #42120b; + --surface-diff-delete-weak: #580f06; + --surface-diff-delete-weaker: #42120b; + --surface-diff-delete-strong: #6a1206; + --surface-diff-delete-stronger: #faa494; + --input-base: #1c1c1c; + --input-hover: #1c1c1c; + --input-active: #091123; + --input-selected: #0b1730; + --input-focus: #091123; + --input-disabled: #282828; + --text-base: rgba(255, 255, 255, 0.618); + --text-weak: rgba(255, 255, 255, 0.422); + --text-weaker: rgba(255, 255, 255, 0.284); + --text-strong: rgba(255, 255, 255, 0.936); + --text-invert-base: #a0a0a0; + --text-invert-weak: #707070; + --text-invert-weaker: #505050; + --text-invert-strong: #ededed; + --text-interactive-base: #9dbefe; + --text-on-brand-base: rgba(255, 255, 255, 0.603); + --text-on-interactive-base: #ededed; + --text-on-interactive-weak: rgba(255, 255, 255, 0.603); + --text-on-success-base: #12c905; + --text-on-critical-base: #fc533a; + --text-on-critical-weak: #b72d1a; + --text-on-critical-strong: #ffe0da; + --text-on-warning-base: rgba(255, 255, 255, 0.603); + --text-on-info-base: rgba(255, 255, 255, 0.603); + --text-diff-add-base: #9bcd97; + --text-diff-delete-base: #fc533a; + --text-diff-delete-strong: #ffe0da; + --text-diff-add-strong: #4a7348; + --text-on-info-weak: rgba(255, 255, 255, 0.404); + --text-on-info-strong: rgba(255, 255, 255, 0.928); + --text-on-warning-weak: rgba(255, 255, 255, 0.404); + --text-on-warning-strong: rgba(255, 255, 255, 0.928); + --text-on-success-weak: #127d0d; + --text-on-success-strong: #bafdb3; + --text-on-brand-weak: rgba(255, 255, 255, 0.404); + --text-on-brand-weaker: rgba(255, 255, 255, 0.266); + --text-on-brand-strong: rgba(255, 255, 255, 0.928); + --button-primary-base: #ededed; + --button-secondary-base: #1c1c1c; + --button-secondary-hover: rgba(255, 255, 255, 0.039); + --button-ghost-hover: rgba(255, 255, 255, 0.031); + --button-ghost-hover2: rgba(255, 255, 255, 0.059); + --border-base: rgba(255, 255, 255, 0.195); + --border-hover: rgba(255, 255, 255, 0.284); + --border-active: rgba(255, 255, 255, 0.418); + --border-selected: #9dbefe; + --border-disabled: rgba(255, 255, 255, 0.284); + --border-focus: rgba(255, 255, 255, 0.418); + --border-weak-base: #282828; + --border-strong-base: rgba(255, 255, 255, 0.266); + --border-strong-hover: rgba(255, 255, 255, 0.266); + --border-strong-active: rgba(255, 255, 255, 0.266); + --border-strong-selected: rgba(3, 76, 255, 0.62); + --border-strong-disabled: rgba(255, 255, 255, 0.138); + --border-strong-focus: rgba(255, 255, 255, 0.266); + --border-weak-hover: rgba(255, 255, 255, 0.181); + --border-weak-active: rgba(255, 255, 255, 0.266); + --border-weak-selected: rgba(3, 76, 255, 0.62); + --border-weak-disabled: rgba(255, 255, 255, 0.138); + --border-weak-focus: rgba(255, 255, 255, 0.266); + --border-weaker-base: #202020; + --border-weaker-hover: rgba(255, 255, 255, 0.084); + --border-weaker-active: rgba(255, 255, 255, 0.138); + --border-weaker-selected: rgba(3, 76, 255, 0.32); + --border-weaker-disabled: rgba(255, 255, 255, 0.034); + --border-weaker-focus: rgba(255, 255, 255, 0.138); + --border-interactive-base: #a3c1fd; + --border-interactive-hover: #7ea9ff; + --border-interactive-active: #034cff; + --border-interactive-selected: #034cff; + --border-interactive-disabled: #505050; + --border-interactive-focus: #034cff; + --border-success-base: #96ec8e; + --border-success-hover: #7add71; + --border-success-selected: #12c905; + --border-warning-base: #e9d282; + --border-warning-hover: #dac063; + --border-warning-selected: #fcd53a; + --border-critical-base: #6a1206; + --border-critical-hover: #952414; + --border-critical-selected: #fc533a; + --border-info-base: #eac5ec; + --border-info-hover: #dab1dd; + --border-info-selected: #edb2f1; + --border-color: #ffffff; + --icon-base: #7e7e7e; + --icon-hover: #a0a0a0; + --icon-active: #ededed; + --icon-selected: #ededed; + --icon-disabled: #3e3e3e; + --icon-focus: #ededed; + --icon-invert-base: #161616; + --icon-weak-base: #343434; + --icon-weak-hover: #d9d9d9; + --icon-weak-active: #c8c8c8; + --icon-weak-selected: #707070; + --icon-weak-disabled: #ededed; + --icon-weak-focus: #707070; + --icon-strong-base: #ededed; --icon-strong-hover: #f6f3f3; --icon-strong-active: #fcfcfc; --icon-strong-selected: #fdfcfc; - --icon-strong-disabled: var(--smoke-dark-8); + --icon-strong-disabled: #3e3e3e; --icon-strong-focus: #fdfcfc; - --icon-brand-base: var(--white); - --icon-interactive-base: var(--cobalt-dark-11); - --icon-success-base: var(--apple-dark-7); - --icon-success-hover: var(--apple-dark-8); - --icon-success-active: var(--apple-dark-11); - --icon-warning-base: var(--amber-dark-9); - --icon-warning-hover: var(--amber-dark-8); - --icon-warning-active: var(--amber-dark-11); - --icon-critical-base: var(--ember-dark-9); - --icon-critical-hover: var(--ember-dark-11); - --icon-critical-active: var(--ember-dark-12); - --icon-info-base: var(--lilac-dark-7); - --icon-info-hover: var(--lilac-dark-8); - --icon-info-active: var(--lilac-dark-11); - --icon-on-brand-base: var(--smoke-light-alpha-11); - --icon-on-brand-hover: var(--smoke-light-alpha-12); - --icon-on-brand-selected: var(--smoke-light-alpha-12); - --icon-on-interactive-base: var(--smoke-dark-12); - --icon-agent-plan-base: var(--purple-dark-9); - --icon-agent-docs-base: var(--amber-dark-9); - --icon-agent-ask-base: var(--cyan-dark-9); - --icon-agent-build-base: var(--cobalt-dark-11); - --icon-on-success-base: var(--apple-dark-alpha-9); - --icon-on-success-hover: var(--apple-dark-alpha-10); - --icon-on-success-selected: var(--apple-dark-alpha-11); - --icon-on-warning-base: var(--amber-darkalpha-9); - --icon-on-warning-hover: var(--amber-darkalpha-10); - --icon-on-warning-selected: var(--amber-darkalpha-11); - --icon-on-critical-base: var(--ember-dark-alpha-9); - --icon-on-critical-hover: var(--ember-dark-alpha-10); - --icon-on-critical-selected: var(--ember-dark-alpha-11); - --icon-on-info-base: var(--lilac-dark-9); - --icon-on-info-hover: var(--lilac-dark-alpha-10); - --icon-on-info-selected: var(--lilac-dark-alpha-11); - --icon-diff-add-base: var(--mint-dark-11); - --icon-diff-add-hover: var(--mint-dark-10); - --icon-diff-add-active: var(--mint-dark-11); - --icon-diff-delete-base: var(--ember-dark-9); - --icon-diff-delete-hover: var(--ember-dark-10); + --icon-brand-base: #ffffff; + --icon-interactive-base: #034cff; + --icon-success-base: #12c905; + --icon-success-hover: #35c02d; + --icon-success-active: #4de144; + --icon-warning-base: #fbb73c; + --icon-warning-hover: #885e08; + --icon-warning-active: #f1b13f; + --icon-critical-base: #fc533a; + --icon-critical-hover: #faa494; + --icon-critical-active: #ffe0da; + --icon-info-base: #68446b; + --icon-info-hover: #815484; + --icon-info-active: #dfa7e3; + --icon-on-brand-base: rgba(255, 255, 255, 0.603); + --icon-on-brand-hover: rgba(255, 255, 255, 0.928); + --icon-on-brand-selected: rgba(255, 255, 255, 0.928); + --icon-on-interactive-base: #ededed; + --icon-agent-plan-base: #edb2f1; + --icon-agent-docs-base: #fbb73c; + --icon-agent-ask-base: #2090f5; + --icon-agent-build-base: #9dbefe; + --icon-on-success-base: rgba(18, 201, 5, 0.9); + --icon-on-success-hover: rgba(53, 192, 45, 0.9); + --icon-on-success-selected: rgba(77, 225, 68, 0.9); + --icon-on-warning-base: rgba(251, 183, 60, 0.9); + --icon-on-warning-hover: rgba(245, 178, 56, 0.9); + --icon-on-warning-selected: rgba(241, 177, 63, 0.9); + --icon-on-critical-base: rgba(252, 83, 58, 0.9); + --icon-on-critical-hover: rgba(245, 79, 54, 0.9); + --icon-on-critical-selected: rgba(250, 164, 148, 0.9); + --icon-on-info-base: #edb2f1; + --icon-on-info-hover: rgba(231, 173, 235, 0.9); + --icon-on-info-selected: rgba(223, 167, 227, 0.9); + --icon-diff-add-base: #9bcd97; + --icon-diff-add-hover: #c3f9bf; + --icon-diff-add-active: #9bcd97; + --icon-diff-delete-base: #fc533a; + --icon-diff-delete-hover: #f54f36; --icon-diff-modified-base: #ffba92; --syntax-comment: var(--text-weak); --syntax-regexp: var(--text-base); @@ -559,12 +570,12 @@ --syntax-constant: #93e9f6; --syntax-punctuation: var(--text-weak); --syntax-object: var(--text-strong); - --syntax-success: var(--apple-dark-10); - --syntax-warning: var(--amber-dark-10); - --syntax-critical: var(--ember-dark-10); + --syntax-success: #35c02d; + --syntax-warning: #f5b238; + --syntax-critical: #f54f36; --syntax-info: #93e9f6; - --syntax-diff-add: var(--mint-dark-11); - --syntax-diff-delete: var(--ember-dark-11); + --syntax-diff-add: #9bcd97; + --syntax-diff-delete: #faa494; --syntax-diff-unknown: #ff0000; --markdown-heading: #9d7cd8; --markdown-text: #eeeeee; @@ -580,10 +591,6 @@ --markdown-image: #fab283; --markdown-image-text: #56b6c2; --markdown-code-block: #eeeeee; - --border-color: #ffffff; - --border-weaker-base: var(--smoke-dark-alpha-2); - --button-ghost-hover: var(--smoke-dark-alpha-2); - --button-ghost-hover2: var(--smoke-dark-alpha-3); --avatar-background-pink: #501b3f; --avatar-background-mint: #033a34; --avatar-background-orange: #5f2a06; @@ -596,5 +603,6 @@ --avatar-text-purple: #9d5bd2; --avatar-text-cyan: #369eff; --avatar-text-lime: #c4f042; + --text-stronger: rgba(255, 255, 255, 0.936); } } diff --git a/packages/ui/src/theme/color.ts b/packages/ui/src/theme/color.ts index f0e15211e9..89d9a653d7 100644 --- a/packages/ui/src/theme/color.ts +++ b/packages/ui/src/theme/color.ts @@ -1,16 +1,25 @@ import type { HexColor, OklchColor } from "./types" +function clamp(v: number, min: number, max: number) { + return Math.max(min, Math.min(max, v)) +} + +function hue(v: number) { + return ((v % 360) + 360) % 360 +} + export function hexToRgb(hex: HexColor): { r: number; g: number; b: number } { const h = hex.replace("#", "") const full = - h.length === 3 + h.length === 3 || h.length === 4 ? h .split("") .map((c) => c + c) .join("") : h + const rgb = full.length === 8 ? full.slice(0, 6) : full - const num = parseInt(full, 16) + const num = parseInt(rgb, 16) return { r: ((num >> 16) & 255) / 255, g: ((num >> 8) & 255) / 255, @@ -20,7 +29,7 @@ export function hexToRgb(hex: HexColor): { r: number; g: number; b: number } { export function rgbToHex(r: number, g: number, b: number): HexColor { const toHex = (v: number) => { - const clamped = Math.max(0, Math.min(1, v)) + const clamped = clamp(v, 0, 1) const int = Math.round(clamped * 255) return int.toString(16).padStart(2, "0") } @@ -91,8 +100,33 @@ export function hexToOklch(hex: HexColor): OklchColor { return rgbToOklch(r, g, b) } +export function fitOklch(oklch: OklchColor): OklchColor { + const base = { + l: clamp(oklch.l, 0, 1), + c: Math.max(0, oklch.c), + h: hue(oklch.h), + } + + const rgb = oklchToRgb(base) + if (rgb.r >= 0 && rgb.r <= 1 && rgb.g >= 0 && rgb.g <= 1 && rgb.b >= 0 && rgb.b <= 1) { + return base + } + + let c = base.c + for (let i = 0; i < 24; i++) { + c *= 0.9 + const next = { ...base, c } + const out = oklchToRgb(next) + if (out.r >= 0 && out.r <= 1 && out.g >= 0 && out.g <= 1 && out.b >= 0 && out.b <= 1) { + return next + } + } + + return { ...base, c: 0 } +} + export function oklchToHex(oklch: OklchColor): HexColor { - const { r, g, b } = oklchToRgb(oklch) + const { r, g, b } = oklchToRgb(fitOklch(oklch)) return rgbToHex(r, g, b) } @@ -101,12 +135,12 @@ export function generateScale(seed: HexColor, isDark: boolean): HexColor[] { const scale: HexColor[] = [] const lightSteps = isDark - ? [0.15, 0.18, 0.22, 0.26, 0.32, 0.38, 0.46, 0.56, base.l, base.l - 0.05, 0.75, 0.93] - : [0.99, 0.97, 0.94, 0.9, 0.85, 0.79, 0.72, 0.64, base.l, base.l + 0.05, 0.45, 0.25] + ? [0.182, 0.21, 0.261, 0.302, 0.341, 0.387, 0.443, 0.514, base.l, Math.max(0, base.l - 0.017), 0.8, 0.93] + : [0.993, 0.983, 0.962, 0.936, 0.906, 0.866, 0.811, 0.74, base.l, Math.max(0, base.l - 0.036), 0.548, 0.33] const chromaMultipliers = isDark - ? [0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.85, 1, 1, 0.9, 0.6] - : [0.1, 0.15, 0.25, 0.35, 0.45, 0.55, 0.7, 0.85, 1, 1, 0.95, 0.85] + ? [0.205, 0.275, 0.46, 0.62, 0.71, 0.79, 0.87, 0.97, 1.04, 1.03, 1, 0.58] + : [0.045, 0.128, 0.34, 0.5, 0.61, 0.69, 0.77, 0.89, 1, 1, 0.97, 0.56] for (let i = 0; i < 12; i++) { scale.push( @@ -127,8 +161,8 @@ export function generateNeutralScale(seed: HexColor, isDark: boolean): HexColor[ const neutralChroma = Math.min(base.c, 0.02) const lightSteps = isDark - ? [0.13, 0.16, 0.2, 0.24, 0.28, 0.33, 0.4, 0.52, 0.58, 0.66, 0.82, 0.96] - : [0.995, 0.98, 0.96, 0.94, 0.91, 0.88, 0.84, 0.78, 0.62, 0.56, 0.46, 0.2] + ? [0.2, 0.226, 0.256, 0.277, 0.301, 0.325, 0.364, 0.431, base.l, 0.593, 0.706, 0.946] + : [0.991, 0.979, 0.964, 0.946, 0.931, 0.913, 0.891, 0.83, base.l, 0.617, 0.542, 0.205] for (let i = 0; i < 12; i++) { scale.push( @@ -164,19 +198,39 @@ export function generateAlphaScale(scale: HexColor[], isDark: boolean): HexColor export function mixColors(color1: HexColor, color2: HexColor, amount: number): HexColor { const c1 = hexToOklch(color1) const c2 = hexToOklch(color2) + const delta = ((((c2.h - c1.h) % 360) + 540) % 360) - 180 return oklchToHex({ l: c1.l + (c2.l - c1.l) * amount, c: c1.c + (c2.c - c1.c) * amount, - h: c1.h + (c2.h - c1.h) * amount, + h: c1.h + delta * amount, }) } +export function shift(color: HexColor, value: { l?: number; c?: number; h?: number }): HexColor { + const base = hexToOklch(color) + return oklchToHex({ + l: base.l + (value.l ?? 0), + c: base.c * (value.c ?? 1), + h: base.h + (value.h ?? 0), + }) +} + +export function blend(color: HexColor, background: HexColor, alpha: number): HexColor { + const fg = hexToRgb(color) + const bg = hexToRgb(background) + return rgbToHex( + fg.r * alpha + bg.r * (1 - alpha), + fg.g * alpha + bg.g * (1 - alpha), + fg.b * alpha + bg.b * (1 - alpha), + ) +} + export function lighten(color: HexColor, amount: number): HexColor { const oklch = hexToOklch(color) return oklchToHex({ ...oklch, - l: Math.min(1, oklch.l + amount), + l: clamp(oklch.l + amount, 0, 1), }) } @@ -184,7 +238,7 @@ export function darken(color: HexColor, amount: number): HexColor { const oklch = hexToOklch(color) return oklchToHex({ ...oklch, - l: Math.max(0, oklch.l - amount), + l: clamp(oklch.l - amount, 0, 1), }) } diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx index c1c1637d67..600c6121c3 100644 --- a/packages/ui/src/theme/context.tsx +++ b/packages/ui/src/theme/context.tsx @@ -35,7 +35,7 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da const tokens = resolveThemeVariant(variant, isDark) const css = themeToCss(tokens) - if (themeId !== "oc-1") { + if (themeId !== "oc-2") { try { localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css) } catch {} @@ -54,7 +54,7 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da } function cacheThemeVariants(theme: DesktopTheme, themeId: string) { - if (themeId === "oc-1") return + if (themeId === "oc-2") return for (const mode of ["light", "dark"] as const) { const isDark = mode === "dark" const variant = isDark ? theme.dark : theme.light @@ -71,7 +71,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ init: (props: { defaultTheme?: string }) => { const [store, setStore] = createStore({ themes: DEFAULT_THEMES as Record, - themeId: props.defaultTheme ?? "oc-1", + themeId: props.defaultTheme ?? "oc-2", colorScheme: "system" as ColorScheme, mode: getSystemMode(), previewThemeId: null as string | null, diff --git a/packages/ui/src/theme/default-themes.ts b/packages/ui/src/theme/default-themes.ts index 52b2b42eba..657d21c3cc 100644 --- a/packages/ui/src/theme/default-themes.ts +++ b/packages/ui/src/theme/default-themes.ts @@ -34,8 +34,8 @@ export const gruvboxTheme = gruvboxThemeJson as DesktopTheme export const auraTheme = auraThemeJson as DesktopTheme export const DEFAULT_THEMES: Record = { - "oc-1": oc1Theme, "oc-2": oc2Theme, + "oc-1": oc1Theme, aura: auraTheme, ayu: ayuTheme, carbonfox: carbonfoxTheme, diff --git a/packages/ui/src/theme/desktop-theme.schema.json b/packages/ui/src/theme/desktop-theme.schema.json index b60a8f37ca..d4f1ffd21f 100644 --- a/packages/ui/src/theme/desktop-theme.schema.json +++ b/packages/ui/src/theme/desktop-theme.schema.json @@ -36,12 +36,13 @@ }, "ColorValue": { "type": "string", - "pattern": "^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|var\(--[a-z0-9-]+\))$", + "pattern": "^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|var\\(--[a-z0-9-]+\\))$", "description": "Either a hex color value (#rgb/#rgba/#rrggbb/#rrggbbaa) or a CSS variable reference" }, "ThemeSeedColors": { "type": "object", - "description": "The minimum set of colors needed to generate a theme", + "description": "The legacy semantic seed set used to generate a theme", + "additionalProperties": false, "required": ["neutral", "primary", "success", "warning", "error", "info", "interactive", "diffAdd", "diffDelete"], "properties": { "neutral": { @@ -82,14 +83,70 @@ } } }, + "ThemePaletteColors": { + "type": "object", + "description": "A compact semantic palette used to derive the full theme programmatically", + "additionalProperties": false, + "required": ["neutral", "primary", "success", "warning", "error", "info"], + "properties": { + "neutral": { + "$ref": "#/definitions/HexColor", + "description": "Base neutral color for generating the gray scale" + }, + "ink": { + "$ref": "#/definitions/HexColor", + "description": "Optional foreground or chrome color used to derive text and border tones" + }, + "primary": { + "$ref": "#/definitions/HexColor", + "description": "Primary brand color used for brand surfaces and strong emphasis" + }, + "success": { + "$ref": "#/definitions/HexColor", + "description": "Success state color" + }, + "warning": { + "$ref": "#/definitions/HexColor", + "description": "Warning state color" + }, + "error": { + "$ref": "#/definitions/HexColor", + "description": "Error or critical state color" + }, + "info": { + "$ref": "#/definitions/HexColor", + "description": "Informational state color" + }, + "accent": { + "$ref": "#/definitions/HexColor", + "description": "Optional extra expressive accent for syntax and rich content" + }, + "interactive": { + "$ref": "#/definitions/HexColor", + "description": "Optional dedicated interactive color; falls back to primary" + }, + "diffAdd": { + "$ref": "#/definitions/HexColor", + "description": "Optional diff-add seed; falls back to a softened success color" + }, + "diffDelete": { + "$ref": "#/definitions/HexColor", + "description": "Optional diff-delete seed; falls back to error" + } + } + }, "ThemeVariant": { "type": "object", - "description": "A theme variant (light or dark) with seed colors and optional overrides", - "required": ["seeds"], + "description": "A theme variant (light or dark) with either a compact palette or legacy seeds and optional overrides", + "oneOf": [{ "required": ["seeds"] }, { "required": ["palette"] }], "properties": { "seeds": { "$ref": "#/definitions/ThemeSeedColors", - "description": "Seed colors used to generate the full palette" + "description": "Legacy seed colors used to generate the full palette" + }, + "palette": { + "$ref": "#/definitions/ThemePaletteColors", + "description": "Compact palette used to derive the full token set" }, "overrides": { "type": "object", diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts index d2c60179ec..1e6fb79324 100644 --- a/packages/ui/src/theme/index.ts +++ b/packages/ui/src/theme/index.ts @@ -1,5 +1,6 @@ export type { DesktopTheme, + ThemePaletteColors, ThemeSeedColors, ThemeVariant, HexColor, @@ -19,7 +20,10 @@ export { generateScale, generateNeutralScale, generateAlphaScale, + fitOklch, + blend, mixColors, + shift, lighten, darken, withAlpha, diff --git a/packages/ui/src/theme/loader.ts b/packages/ui/src/theme/loader.ts index 0f61076a00..4d48000daf 100644 --- a/packages/ui/src/theme/loader.ts +++ b/packages/ui/src/theme/loader.ts @@ -27,7 +27,7 @@ export function applyTheme(theme: DesktopTheme, themeId?: string): void { } function buildThemeCss(light: ResolvedTheme, dark: ResolvedTheme, themeId: string): string { - const isDefaultTheme = themeId === "oc-1" + const isDefaultTheme = themeId === "oc-2" const lightCss = themeToCss(light) const darkCss = themeToCss(dark) diff --git a/packages/ui/src/theme/resolve.ts b/packages/ui/src/theme/resolve.ts index e8fca41036..722648dabc 100644 --- a/packages/ui/src/theme/resolve.ts +++ b/packages/ui/src/theme/resolve.ts @@ -1,27 +1,131 @@ import type { ColorValue, DesktopTheme, HexColor, ResolvedTheme, ThemeVariant } from "./types" -import { generateNeutralScale, generateScale, hexToOklch, oklchToHex, withAlpha } from "./color" +import { blend, generateNeutralScale, generateScale, hexToOklch, oklchToHex, shift, withAlpha } from "./color" export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): ResolvedTheme { - const { seeds, overrides = {} } = variant + const colors = getColors(variant) + const { overrides = {} } = variant - const neutral = generateNeutralScale(seeds.neutral, isDark) - const primary = generateScale(seeds.primary, isDark) - const success = generateScale(seeds.success, isDark) - const warning = generateScale(seeds.warning, isDark) - const error = generateScale(seeds.error, isDark) - const info = generateScale(seeds.info, isDark) - const interactive = generateScale(seeds.interactive, isDark) - const diffAdd = generateScale(seeds.diffAdd, isDark) - const diffDelete = generateScale(seeds.diffDelete, isDark) + const neutral = generateNeutralScale(colors.neutral, isDark) + const primary = generateScale(colors.primary, isDark) + const accent = generateScale(colors.accent, isDark) + const success = generateScale(colors.success, isDark) + const warning = generateScale(colors.warning, isDark) + const error = generateScale(colors.error, isDark) + const info = generateScale(colors.info, isDark) + const interactive = generateScale(colors.interactive, isDark) + const hasInk = colors.compact && Boolean(colors.ink) + const noInk = colors.compact && !hasInk + const shadow = noInk && !isDark ? generateNeutralScale(colors.neutral, true) : neutral + const amber = generateScale( + shift(colors.warning, isDark ? { h: -16, l: -0.058, c: 1.14 } : { h: -22, l: -0.082, c: 0.94 }), + isDark, + ) + const blue = generateScale(shift(colors.interactive, { h: -12, l: 0.128, c: 1.12 }), isDark) + const brandl = noInk && isDark ? generateScale(colors.primary, false) : primary + const successl = noInk && isDark ? generateScale(colors.success, false) : success + const warningl = noInk && isDark ? generateScale(colors.warning, false) : warning + const infol = noInk && isDark ? generateScale(colors.info, false) : info + const interl = noInk && isDark ? generateScale(colors.interactive, false) : interactive + const diffAdd = generateScale( + colors.diffAdd ?? + (noInk + ? shift(colors.success, { c: isDark ? 0.54 : 0.6, l: isDark ? 0.22 : 0.16 }) + : shift(colors.success, { c: isDark ? 0.7 : 0.55, l: isDark ? -0.18 : 0.14 })), + isDark, + ) + const diffDelete = generateScale( + colors.diffDelete ?? + (noInk ? colors.error : shift(colors.error, { c: isDark ? 0.82 : 0.7, l: isDark ? -0.08 : 0.08 })), + isDark, + ) + const ink = colors.ink ?? colors.neutral + const backgroundOverride = overrides["background-base"] + const backgroundHex = getHex(backgroundOverride) + const overlay = noInk || (Boolean(backgroundOverride) && !backgroundHex) + const content = (seed: HexColor, scale: HexColor[]) => { + const value = isDark ? seed : hexToOklch(seed).l > 0.82 ? scale[10] : seed + return shift(value, { c: isDark ? 1.16 : 1.1 }) + } + const modified = () => { + if (!colors.compact) return isDark ? "#ffba92" : "#FF8C00" + if (!hasInk) return isDark ? "#ffba92" : "#FF8C00" + const warningHue = hexToOklch(colors.warning).h + const deleteHue = hexToOklch(colors.diffDelete ?? colors.error).h + const delta = Math.abs(((((deleteHue - warningHue) % 360) + 540) % 360) - 180) + if (delta < 48) return isDark ? "#ffba92" : "#FF8C00" + return content(colors.warning, warning) + } + const surface = ( + seed: HexColor, + alpha: { base: number; weak: number; weaker: number; strong: number; stronger: number }, + ) => { + const base = alphaTone(seed, alpha.base) + return { + base, + weak: alphaTone(seed, alpha.weak), + weaker: alphaTone(seed, alpha.weaker), + strong: alphaTone(seed, alpha.strong), + stronger: alphaTone(seed, alpha.stronger), + } + } + const compactBackground = + colors.compact && !hasInk + ? isDark + ? { + base: shift(blend(colors.neutral, "#000000", 0.145), { c: 0 }), + weak: shift(blend(colors.neutral, "#000000", 0.27), { c: 0 }), + strong: shift(blend(colors.neutral, "#000000", 0.165), { c: 0 }), + stronger: shift(blend(colors.neutral, "#000000", 0.19), { c: 0 }), + } + : { + base: blend(colors.neutral, "#ffffff", 0.066), + weak: blend(colors.neutral, "#ffffff", 0.11), + strong: blend(colors.neutral, "#ffffff", 0.024), + stronger: blend(colors.neutral, "#ffffff", 0.024), + } + : undefined + const compactInkBackground = + colors.compact && hasInk && isDark + ? { + base: neutral[2], + weak: neutral[3], + strong: neutral[1], + stronger: neutral[2], + } + : undefined - const neutralAlpha = generateNeutralAlphaScale(neutral, isDark) + const background = backgroundHex ?? compactInkBackground?.base ?? compactBackground?.base ?? neutral[0] + const alphaTone = (color: HexColor, alpha: number) => + overlay ? (withAlpha(color, alpha) as ColorValue) : blend(color, background, alpha) + const borderTone = (light: number, dark: number) => + alphaTone( + ink, + isDark ? Math.min(1, dark + 0.024 + (colors.compact && hasInk ? 0.08 : 0)) : Math.min(1, light + 0.024), + ) + const diffHiddenSurface = noInk + ? { + base: blue[isDark ? 1 : 2], + weak: blue[isDark ? 0 : 1], + weaker: blue[isDark ? 2 : 0], + strong: blue[4], + stronger: blue[isDark ? 10 : 8], + } + : surface( + isDark ? shift(colors.interactive, { c: 0.55, l: 0 }) : shift(colors.interactive, { c: 0.45, l: 0.08 }), + isDark + ? { base: 0.14, weak: 0.08, weaker: 0.18, strong: 0.26, stronger: 0.42 } + : { base: 0.12, weak: 0.08, weaker: 0.16, strong: 0.24, stronger: 0.36 }, + ) + + const neutralAlpha = noInk ? generateNeutralOverlayScale(neutral, isDark) : generateNeutralAlphaScale(neutral, isDark) const tokens: ResolvedTheme = {} - tokens["background-base"] = neutral[0] - tokens["background-weak"] = neutral[2] - tokens["background-strong"] = neutral[0] - tokens["background-stronger"] = isDark ? neutral[1] : "#fcfcfc" + tokens["background-base"] = compactInkBackground?.base ?? compactBackground?.base ?? neutral[0] + tokens["background-weak"] = compactInkBackground?.weak ?? compactBackground?.weak ?? neutral[2] + tokens["background-strong"] = compactInkBackground?.strong ?? compactBackground?.strong ?? neutral[0] + tokens["background-stronger"] = + compactInkBackground?.stronger ?? compactBackground?.stronger ?? (isDark ? neutral[1] : "#fcfcfc") tokens["surface-base"] = neutralAlpha[1] tokens["base"] = neutralAlpha[1] @@ -37,8 +141,8 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res : (withAlpha(neutral[3], 0.09) as ColorValue) tokens["surface-inset-strong-hover"] = tokens["surface-inset-strong"] tokens["surface-raised-base"] = neutralAlpha[0] - tokens["surface-float-base"] = isDark ? neutral[0] : neutral[11] - tokens["surface-float-base-hover"] = isDark ? neutral[1] : neutral[10] + tokens["surface-float-base"] = isDark ? neutral[0] : noInk ? shadow[0] : neutral[11] + tokens["surface-float-base-hover"] = isDark ? neutral[1] : noInk ? shadow[1] : neutral[10] tokens["surface-raised-base-hover"] = neutralAlpha[1] tokens["surface-raised-base-active"] = neutralAlpha[2] tokens["surface-raised-strong"] = isDark ? neutralAlpha[3] : neutral[0] @@ -50,34 +154,34 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res tokens["surface-strong"] = isDark ? neutralAlpha[6] : "#ffffff" tokens["surface-raised-stronger-non-alpha"] = isDark ? neutral[2] : "#ffffff" - tokens["surface-brand-base"] = primary[8] - tokens["surface-brand-hover"] = primary[9] + tokens["surface-brand-base"] = brandl[8] + tokens["surface-brand-hover"] = brandl[9] - tokens["surface-interactive-base"] = interactive[2] - tokens["surface-interactive-hover"] = interactive[3] - tokens["surface-interactive-weak"] = interactive[1] - tokens["surface-interactive-weak-hover"] = interactive[2] + tokens["surface-interactive-base"] = interactive[isDark ? 4 : 3] + tokens["surface-interactive-hover"] = interactive[isDark ? 5 : 4] + tokens["surface-interactive-weak"] = interactive[isDark ? 3 : 2] + tokens["surface-interactive-weak-hover"] = noInk && isDark ? interl[4] : interactive[isDark ? 4 : 3] - tokens["surface-success-base"] = success[2] - tokens["surface-success-weak"] = success[1] - tokens["surface-success-strong"] = success[8] - tokens["surface-warning-base"] = warning[2] - tokens["surface-warning-weak"] = warning[1] - tokens["surface-warning-strong"] = warning[8] - tokens["surface-critical-base"] = error[2] - tokens["surface-critical-weak"] = error[1] - tokens["surface-critical-strong"] = error[8] - tokens["surface-info-base"] = info[2] - tokens["surface-info-weak"] = info[1] - tokens["surface-info-strong"] = info[8] + tokens["surface-success-base"] = success[isDark ? 4 : 3] + tokens["surface-success-weak"] = success[isDark ? 3 : 2] + tokens["surface-success-strong"] = success[9] + tokens["surface-warning-base"] = (noInk && isDark ? warningl : warning)[isDark ? 4 : 3] + tokens["surface-warning-weak"] = (noInk && isDark ? warningl : warning)[isDark ? 3 : 2] + tokens["surface-warning-strong"] = (noInk && isDark ? warningl : warning)[9] + tokens["surface-critical-base"] = error[isDark ? 4 : 3] + tokens["surface-critical-weak"] = error[isDark ? 3 : 2] + tokens["surface-critical-strong"] = error[9] + tokens["surface-info-base"] = (noInk && isDark ? infol : info)[isDark ? 4 : 3] + tokens["surface-info-weak"] = (noInk && isDark ? infol : info)[isDark ? 3 : 2] + tokens["surface-info-strong"] = (noInk && isDark ? infol : info)[9] tokens["surface-diff-unchanged-base"] = isDark ? neutral[0] : "#ffffff00" tokens["surface-diff-skip-base"] = isDark ? neutralAlpha[0] : neutral[1] - tokens["surface-diff-hidden-base"] = interactive[isDark ? 1 : 2] - tokens["surface-diff-hidden-weak"] = interactive[isDark ? 0 : 1] - tokens["surface-diff-hidden-weaker"] = interactive[isDark ? 2 : 0] - tokens["surface-diff-hidden-strong"] = interactive[4] - tokens["surface-diff-hidden-stronger"] = interactive[isDark ? 10 : 8] + tokens["surface-diff-hidden-base"] = diffHiddenSurface.base + tokens["surface-diff-hidden-weak"] = diffHiddenSurface.weak + tokens["surface-diff-hidden-weaker"] = diffHiddenSurface.weaker + tokens["surface-diff-hidden-strong"] = diffHiddenSurface.strong + tokens["surface-diff-hidden-stronger"] = diffHiddenSurface.stronger tokens["surface-diff-add-base"] = diffAdd[2] tokens["surface-diff-add-weak"] = diffAdd[isDark ? 3 : 1] tokens["surface-diff-add-weaker"] = diffAdd[isDark ? 2 : 0] @@ -96,10 +200,36 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res tokens["input-focus"] = interactive[0] tokens["input-disabled"] = neutral[3] - tokens["text-base"] = neutral[10] - tokens["text-weak"] = neutral[8] - tokens["text-weaker"] = neutral[7] - tokens["text-strong"] = neutral[11] + tokens["text-base"] = hasInk ? ink : noInk ? (isDark ? neutralAlpha[10] : neutral[10]) : neutral[10] + tokens["text-weak"] = hasInk + ? shift(ink, { l: isDark ? -0.18 : 0.16, c: 0.88 }) + : noInk + ? isDark + ? neutralAlpha[8] + : neutral[8] + : neutral[8] + tokens["text-weaker"] = hasInk + ? shift(ink, { l: isDark ? -0.3 : 0.26, c: isDark ? 0.74 : 0.68 }) + : noInk + ? isDark + ? neutralAlpha[7] + : neutral[7] + : neutral[7] + tokens["text-strong"] = hasInk + ? isDark && colors.compact + ? blend("#ffffff", ink, 0.82) + : shift(ink, { l: isDark ? 0.06 : -0.09, c: 1 }) + : noInk + ? isDark + ? neutralAlpha[11] + : neutral[11] + : neutral[11] + if (noInk && isDark) { + tokens["text-base"] = withAlpha("#ffffff", 0.618) as ColorValue + tokens["text-weak"] = withAlpha("#ffffff", 0.422) as ColorValue + tokens["text-weaker"] = withAlpha("#ffffff", 0.284) as ColorValue + tokens["text-strong"] = withAlpha("#ffffff", 0.936) as ColorValue + } tokens["text-invert-base"] = isDark ? neutral[10] : neutral[1] tokens["text-invert-weak"] = isDark ? neutral[8] : neutral[2] tokens["text-invert-weaker"] = isDark ? neutral[7] : neutral[3] @@ -128,79 +258,166 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res tokens["text-on-brand-weaker"] = neutralAlpha[7] tokens["text-on-brand-strong"] = neutralAlpha[11] - tokens["button-secondary-base"] = isDark ? neutral[2] : neutral[0] - tokens["button-secondary-hover"] = isDark ? neutral[3] : neutral[1] + tokens["button-primary-base"] = neutral[11] + tokens["button-secondary-base"] = noInk ? (isDark ? neutral[1] : neutral[0]) : isDark ? neutral[2] : neutral[0] + tokens["button-secondary-hover"] = noInk ? (isDark ? neutral[1] : neutral[1]) : isDark ? neutral[3] : neutral[1] tokens["button-ghost-hover"] = neutralAlpha[1] tokens["button-ghost-hover2"] = neutralAlpha[2] - tokens["border-base"] = neutralAlpha[6] - tokens["border-hover"] = neutralAlpha[7] - tokens["border-active"] = neutralAlpha[8] - tokens["border-selected"] = withAlpha(interactive[8], isDark ? 0.9 : 0.99) as ColorValue - tokens["border-disabled"] = neutralAlpha[7] - tokens["border-focus"] = neutralAlpha[8] - tokens["border-weak-base"] = neutralAlpha[isDark ? 5 : 4] - tokens["border-strong-base"] = neutralAlpha[isDark ? 7 : 6] - tokens["border-strong-hover"] = neutralAlpha[7] - tokens["border-strong-active"] = neutralAlpha[isDark ? 7 : 6] - tokens["border-strong-selected"] = withAlpha(interactive[5], 0.6) as ColorValue - tokens["border-strong-disabled"] = neutralAlpha[5] - tokens["border-strong-focus"] = neutralAlpha[isDark ? 7 : 6] - tokens["border-weak-hover"] = neutralAlpha[isDark ? 6 : 5] - tokens["border-weak-active"] = neutralAlpha[isDark ? 7 : 6] - tokens["border-weak-selected"] = withAlpha(interactive[4], isDark ? 0.6 : 0.5) as ColorValue - tokens["border-weak-disabled"] = neutralAlpha[5] - tokens["border-weak-focus"] = neutralAlpha[isDark ? 7 : 6] - tokens["border-weaker-base"] = neutralAlpha[2] + if (noInk) { + const tone = (alpha: number) => alphaTone((isDark ? "#ffffff" : "#000000") as HexColor, alpha) + if (isDark) { + tokens["surface-base"] = tone(0.031) + tokens["surface-base-hover"] = tone(0.039) + tokens["surface-base-active"] = tone(0.059) + tokens["surface-raised-base"] = tone(0.059) + tokens["surface-raised-base-hover"] = tone(0.078) + tokens["surface-raised-base-active"] = tone(0.102) + tokens["surface-raised-strong"] = tone(0.078) + tokens["surface-raised-strong-hover"] = tone(0.129) + tokens["surface-raised-stronger"] = tone(0.129) + tokens["surface-raised-stronger-hover"] = tone(0.169) + tokens["surface-weak"] = tone(0.078) + tokens["surface-weaker"] = tone(0.102) + tokens["surface-strong"] = tone(0.169) + tokens["surface-raised-stronger-non-alpha"] = neutral[1] + tokens["surface-inset-base"] = withAlpha("#000000", 0.5) as ColorValue + tokens["surface-inset-base-hover"] = tokens["surface-inset-base"] + tokens["surface-inset-strong"] = withAlpha("#000000", 0.8) as ColorValue + tokens["surface-inset-strong-hover"] = tokens["surface-inset-strong"] + tokens["button-secondary-hover"] = tone(0.039) + tokens["button-ghost-hover"] = tone(0.031) + tokens["button-ghost-hover2"] = tone(0.059) + tokens["input-base"] = neutral[1] + tokens["input-hover"] = neutral[1] + tokens["input-selected"] = interactive[1] + tokens["surface-diff-skip-base"] = "#00000000" + } - tokens["border-interactive-base"] = interactive[6] - tokens["border-interactive-hover"] = interactive[7] - tokens["border-interactive-active"] = interactive[8] - tokens["border-interactive-selected"] = interactive[8] + if (!isDark) { + tokens["surface-base"] = tone(0.031) + tokens["surface-base-hover"] = tone(0.059) + tokens["surface-base-active"] = tone(0.051) + tokens["surface-raised-base"] = tone(0.031) + tokens["surface-raised-base-hover"] = tone(0.051) + tokens["surface-raised-base-active"] = tone(0.09) + tokens["surface-raised-strong"] = neutral[0] + tokens["surface-raised-strong-hover"] = "#ffffff" + tokens["surface-raised-stronger"] = "#ffffff" + tokens["surface-raised-stronger-hover"] = "#ffffff" + tokens["surface-weak"] = tone(0.051) + tokens["surface-weaker"] = tone(0.071) + tokens["surface-strong"] = "#ffffff" + tokens["surface-raised-stronger-non-alpha"] = "#ffffff" + tokens["surface-inset-strong"] = tone(0.09) + tokens["surface-inset-strong-hover"] = tokens["surface-inset-strong"] + tokens["button-secondary-hover"] = blend("#ffffff", background, 0.04) + tokens["button-ghost-hover"] = tone(0.031) + tokens["button-ghost-hover2"] = tone(0.051) + tokens["input-base"] = neutral[0] + tokens["input-hover"] = neutral[1] + } + + tokens["surface-base-interactive-active"] = withAlpha(colors.interactive, isDark ? 0.125 : 0.09) as ColorValue + } + + tokens["border-base"] = hasInk ? borderTone(0.22, 0.16) : neutralAlpha[6] + tokens["border-hover"] = hasInk ? borderTone(0.28, 0.2) : neutralAlpha[7] + tokens["border-active"] = hasInk ? borderTone(0.34, 0.24) : neutralAlpha[8] + tokens["border-selected"] = noInk + ? isDark + ? interactive[10] + : (withAlpha(colors.interactive, 0.99) as ColorValue) + : (withAlpha(interactive[8], isDark ? 0.9 : 0.99) as ColorValue) + tokens["border-disabled"] = hasInk ? borderTone(0.18, 0.12) : neutralAlpha[7] + tokens["border-focus"] = hasInk ? borderTone(0.34, 0.24) : neutralAlpha[8] + tokens["border-weak-base"] = hasInk + ? borderTone(0.1, 0.08) + : noInk + ? isDark + ? neutral[3] + : blend(neutral[4], neutral[5], 0.5) + : neutralAlpha[isDark ? 5 : 4] + tokens["border-strong-base"] = hasInk ? borderTone(0.34, 0.24) : neutralAlpha[isDark ? 7 : 6] + tokens["border-strong-hover"] = hasInk ? borderTone(0.4, 0.28) : neutralAlpha[7] + tokens["border-strong-active"] = hasInk ? borderTone(0.46, 0.32) : neutralAlpha[isDark ? 7 : 6] + tokens["border-strong-selected"] = noInk + ? (withAlpha(colors.interactive, isDark ? 0.62 : 0.31) as ColorValue) + : (withAlpha(interactive[5], 0.6) as ColorValue) + tokens["border-strong-disabled"] = hasInk ? borderTone(0.14, 0.1) : neutralAlpha[5] + tokens["border-strong-focus"] = hasInk ? borderTone(0.46, 0.32) : neutralAlpha[isDark ? 7 : 6] + tokens["border-weak-hover"] = hasInk ? borderTone(0.16, 0.12) : neutralAlpha[isDark ? 6 : 5] + tokens["border-weak-active"] = hasInk ? borderTone(0.22, 0.16) : neutralAlpha[isDark ? 7 : 6] + tokens["border-weak-selected"] = noInk + ? (withAlpha(colors.interactive, isDark ? 0.62 : 0.24) as ColorValue) + : (withAlpha(interactive[4], isDark ? 0.6 : 0.5) as ColorValue) + tokens["border-weak-disabled"] = hasInk ? borderTone(0.08, 0.06) : neutralAlpha[5] + tokens["border-weak-focus"] = hasInk ? borderTone(0.22, 0.16) : neutralAlpha[isDark ? 7 : 6] + tokens["border-weaker-base"] = hasInk + ? borderTone(0.06, 0.04) + : noInk + ? isDark + ? blend(neutral[1], neutral[2], 0.5) + : blend(neutral[2], neutral[3], 0.5) + : neutralAlpha[2] + + if (noInk) { + const line = (l: number, d: number) => alphaTone((isDark ? "#ffffff" : "#000000") as HexColor, isDark ? d : l) + tokens["border-base"] = line(0.162, 0.195) + tokens["border-hover"] = line(0.236, 0.284) + tokens["border-active"] = line(0.46, 0.418) + tokens["border-disabled"] = tokens["border-hover"] + tokens["border-focus"] = tokens["border-active"] + } + + tokens["border-interactive-base"] = (noInk && isDark ? interl : interactive)[6] + tokens["border-interactive-hover"] = (noInk && isDark ? interl : interactive)[7] + tokens["border-interactive-active"] = (noInk && isDark ? interl : interactive)[8] + tokens["border-interactive-selected"] = (noInk && isDark ? interl : interactive)[8] tokens["border-interactive-disabled"] = neutral[7] - tokens["border-interactive-focus"] = interactive[8] + tokens["border-interactive-focus"] = (noInk && isDark ? interl : interactive)[8] - tokens["border-success-base"] = success[5] - tokens["border-success-hover"] = success[6] - tokens["border-success-selected"] = success[8] - tokens["border-warning-base"] = warning[5] - tokens["border-warning-hover"] = warning[6] - tokens["border-warning-selected"] = warning[8] + tokens["border-success-base"] = (noInk && isDark ? successl : success)[5] + tokens["border-success-hover"] = (noInk && isDark ? successl : success)[6] + tokens["border-success-selected"] = (noInk && isDark ? successl : success)[8] + tokens["border-warning-base"] = (noInk && isDark ? warningl : warning)[5] + tokens["border-warning-hover"] = (noInk && isDark ? warningl : warning)[6] + tokens["border-warning-selected"] = (noInk && isDark ? warningl : warning)[8] tokens["border-critical-base"] = error[isDark ? 4 : 5] tokens["border-critical-hover"] = error[6] tokens["border-critical-selected"] = error[8] - tokens["border-info-base"] = info[5] - tokens["border-info-hover"] = info[6] - tokens["border-info-selected"] = info[8] + tokens["border-info-base"] = (noInk && isDark ? infol : info)[5] + tokens["border-info-hover"] = (noInk && isDark ? infol : info)[6] + tokens["border-info-selected"] = (noInk && isDark ? infol : info)[8] tokens["border-color"] = "#ffffff" - tokens["icon-base"] = neutral[8] - tokens["icon-hover"] = neutral[isDark ? 9 : 10] - tokens["icon-active"] = neutral[isDark ? 10 : 11] - tokens["icon-selected"] = neutral[11] + tokens["icon-base"] = hasInk && !isDark ? tokens["text-weak"] : neutral[isDark ? 9 : 8] + tokens["icon-hover"] = hasInk && !isDark ? tokens["text-base"] : neutral[10] + tokens["icon-active"] = hasInk && !isDark ? tokens["text-strong"] : neutral[11] + tokens["icon-selected"] = hasInk && !isDark ? tokens["text-strong"] : neutral[11] tokens["icon-disabled"] = neutral[isDark ? 6 : 7] - tokens["icon-focus"] = neutral[11] + tokens["icon-focus"] = hasInk && !isDark ? tokens["text-strong"] : neutral[11] tokens["icon-invert-base"] = isDark ? neutral[0] : "#ffffff" tokens["icon-weak-base"] = neutral[isDark ? 5 : 6] - tokens["icon-weak-hover"] = neutral[6] - tokens["icon-weak-active"] = neutral[7] - tokens["icon-weak-selected"] = neutral[8] - tokens["icon-weak-disabled"] = neutral[isDark ? 3 : 5] + tokens["icon-weak-hover"] = noInk && isDark ? blend(neutral[11], neutral[10], 0.74) : neutral[isDark ? 11 : 7] + tokens["icon-weak-active"] = noInk && isDark ? blend(neutral[11], neutral[10], 0.52) : neutral[8] + tokens["icon-weak-selected"] = neutral[isDark ? 8 : 9] + tokens["icon-weak-disabled"] = noInk && isDark ? neutral[11] : neutral[isDark ? 3 : 5] tokens["icon-weak-focus"] = neutral[8] tokens["icon-strong-base"] = neutral[11] tokens["icon-strong-hover"] = isDark ? "#f6f3f3" : "#151313" tokens["icon-strong-active"] = isDark ? "#fcfcfc" : "#020202" tokens["icon-strong-selected"] = isDark ? "#fdfcfc" : "#020202" - tokens["icon-strong-disabled"] = neutral[7] + tokens["icon-strong-disabled"] = noInk && isDark ? neutral[6] : neutral[7] tokens["icon-strong-focus"] = isDark ? "#fdfcfc" : "#020202" tokens["icon-brand-base"] = isDark ? "#ffffff" : neutral[11] tokens["icon-interactive-base"] = interactive[8] - tokens["icon-success-base"] = success[isDark ? 6 : 6] - tokens["icon-success-hover"] = success[7] + tokens["icon-success-base"] = success[isDark ? 8 : 6] + tokens["icon-success-hover"] = success[isDark ? 9 : 7] tokens["icon-success-active"] = success[10] - tokens["icon-warning-base"] = warning[6] - tokens["icon-warning-hover"] = warning[7] - tokens["icon-warning-active"] = warning[10] + tokens["icon-warning-base"] = amber[isDark ? 8 : 6] + tokens["icon-warning-hover"] = amber[7] + tokens["icon-warning-active"] = amber[10] tokens["icon-critical-base"] = error[isDark ? 8 : 9] tokens["icon-critical-hover"] = error[10] tokens["icon-critical-active"] = error[11] @@ -213,16 +430,16 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res tokens["icon-on-interactive-base"] = isDark ? neutral[11] : neutral[0] tokens["icon-agent-plan-base"] = info[8] - tokens["icon-agent-docs-base"] = warning[8] - tokens["icon-agent-ask-base"] = interactive[8] + tokens["icon-agent-docs-base"] = amber[8] + tokens["icon-agent-ask-base"] = blue[8] tokens["icon-agent-build-base"] = interactive[isDark ? 10 : 8] tokens["icon-on-success-base"] = withAlpha(success[8], 0.9) as ColorValue tokens["icon-on-success-hover"] = withAlpha(success[9], 0.9) as ColorValue tokens["icon-on-success-selected"] = withAlpha(success[10], 0.9) as ColorValue - tokens["icon-on-warning-base"] = withAlpha(warning[8], 0.9) as ColorValue - tokens["icon-on-warning-hover"] = withAlpha(warning[9], 0.9) as ColorValue - tokens["icon-on-warning-selected"] = withAlpha(warning[10], 0.9) as ColorValue + tokens["icon-on-warning-base"] = withAlpha(amber[8], 0.9) as ColorValue + tokens["icon-on-warning-hover"] = withAlpha(amber[9], 0.9) as ColorValue + tokens["icon-on-warning-selected"] = withAlpha(amber[10], 0.9) as ColorValue tokens["icon-on-critical-base"] = withAlpha(error[8], 0.9) as ColorValue tokens["icon-on-critical-hover"] = withAlpha(error[9], 0.9) as ColorValue tokens["icon-on-critical-selected"] = withAlpha(error[10], 0.9) as ColorValue @@ -235,42 +452,120 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res tokens["icon-diff-add-active"] = diffAdd[isDark ? 10 : 11] tokens["icon-diff-delete-base"] = diffDelete[isDark ? 8 : 9] tokens["icon-diff-delete-hover"] = diffDelete[isDark ? 9 : 10] - tokens["icon-diff-modified-base"] = isDark ? "#ffba92" : "#FF8C00" + tokens["icon-diff-modified-base"] = modified() - tokens["syntax-comment"] = "var(--text-weak)" - tokens["syntax-regexp"] = "var(--text-base)" - tokens["syntax-string"] = isDark ? "#00ceb9" : "#006656" - tokens["syntax-keyword"] = "var(--text-weak)" - tokens["syntax-primitive"] = isDark ? "#ffba92" : "#fb4804" - tokens["syntax-operator"] = isDark ? "var(--text-weak)" : "var(--text-base)" - tokens["syntax-variable"] = "var(--text-strong)" - tokens["syntax-property"] = isDark ? "#ff9ae2" : "#ed6dc8" - tokens["syntax-type"] = isDark ? "#ecf58c" : "#596600" - tokens["syntax-constant"] = isDark ? "#93e9f6" : "#007b80" - tokens["syntax-punctuation"] = isDark ? "var(--text-weak)" : "var(--text-base)" - tokens["syntax-object"] = "var(--text-strong)" - tokens["syntax-success"] = success[9] - tokens["syntax-warning"] = warning[9] - tokens["syntax-critical"] = error[isDark ? 9 : 9] - tokens["syntax-info"] = isDark ? "#93e9f6" : "#0092a8" - tokens["syntax-diff-add"] = diffAdd[10] - tokens["syntax-diff-delete"] = diffDelete[10] - tokens["syntax-diff-unknown"] = "#ff0000" + if (colors.compact) { + if (!hasInk) { + tokens["syntax-comment"] = "var(--text-weak)" + tokens["syntax-regexp"] = "var(--text-base)" + tokens["syntax-string"] = isDark ? "#00ceb9" : "#006656" + tokens["syntax-keyword"] = "var(--text-weak)" + tokens["syntax-primitive"] = isDark ? "#ffba92" : "#fb4804" + tokens["syntax-operator"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-variable"] = "var(--text-strong)" + tokens["syntax-property"] = isDark ? "#ff9ae2" : "#ed6dc8" + tokens["syntax-type"] = isDark ? "#ecf58c" : "#596600" + tokens["syntax-constant"] = isDark ? "#93e9f6" : "#007b80" + tokens["syntax-punctuation"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-object"] = "var(--text-strong)" + tokens["syntax-success"] = success[9] + tokens["syntax-warning"] = amber[9] + tokens["syntax-critical"] = error[9] + tokens["syntax-info"] = isDark ? "#93e9f6" : "#0092a8" + tokens["syntax-diff-add"] = diffAdd[10] + tokens["syntax-diff-delete"] = diffDelete[10] + tokens["syntax-diff-unknown"] = "#ff0000" - tokens["markdown-heading"] = isDark ? "#9d7cd8" : "#d68c27" - tokens["markdown-text"] = isDark ? "#eeeeee" : "#1a1a1a" - tokens["markdown-link"] = isDark ? "#fab283" : "#3b7dd8" - tokens["markdown-link-text"] = isDark ? "#56b6c2" : "#318795" - tokens["markdown-code"] = isDark ? "#7fd88f" : "#3d9a57" - tokens["markdown-block-quote"] = isDark ? "#e5c07b" : "#b0851f" - tokens["markdown-emph"] = isDark ? "#e5c07b" : "#b0851f" - tokens["markdown-strong"] = isDark ? "#f5a742" : "#d68c27" - tokens["markdown-horizontal-rule"] = isDark ? "#808080" : "#8a8a8a" - tokens["markdown-list-item"] = isDark ? "#fab283" : "#3b7dd8" - tokens["markdown-list-enumeration"] = isDark ? "#56b6c2" : "#318795" - tokens["markdown-image"] = isDark ? "#fab283" : "#3b7dd8" - tokens["markdown-image-text"] = isDark ? "#56b6c2" : "#318795" - tokens["markdown-code-block"] = isDark ? "#eeeeee" : "#1a1a1a" + tokens["markdown-heading"] = isDark ? "#9d7cd8" : "#d68c27" + tokens["markdown-text"] = isDark ? "#eeeeee" : "#1a1a1a" + tokens["markdown-link"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-link-text"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-code"] = isDark ? "#7fd88f" : "#3d9a57" + tokens["markdown-block-quote"] = isDark ? "#e5c07b" : "#b0851f" + tokens["markdown-emph"] = isDark ? "#e5c07b" : "#b0851f" + tokens["markdown-strong"] = isDark ? "#f5a742" : "#d68c27" + tokens["markdown-horizontal-rule"] = isDark ? "#808080" : "#8a8a8a" + tokens["markdown-list-item"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-list-enumeration"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-image"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-image-text"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-code-block"] = isDark ? "#eeeeee" : "#1a1a1a" + } + + if (hasInk) { + tokens["syntax-comment"] = "var(--text-weak)" + tokens["syntax-regexp"] = "var(--text-base)" + tokens["syntax-string"] = content(colors.success, success) + tokens["syntax-keyword"] = "var(--text-weak)" + tokens["syntax-primitive"] = content(colors.accent, accent) + tokens["syntax-operator"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-variable"] = "var(--text-strong)" + tokens["syntax-property"] = content(colors.primary, primary) + tokens["syntax-type"] = content(colors.warning, warning) + tokens["syntax-constant"] = content(colors.info, info) + tokens["syntax-punctuation"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-object"] = "var(--text-strong)" + tokens["syntax-success"] = success[9] + tokens["syntax-warning"] = amber[9] + tokens["syntax-critical"] = error[9] + tokens["syntax-info"] = content(colors.info, info) + tokens["syntax-diff-add"] = diffAdd[10] + tokens["syntax-diff-delete"] = diffDelete[10] + tokens["syntax-diff-unknown"] = "#ff0000" + + tokens["markdown-heading"] = content(colors.primary, primary) + tokens["markdown-text"] = tokens["text-base"] + tokens["markdown-link"] = content(colors.interactive, interactive) + tokens["markdown-link-text"] = content(colors.info, info) + tokens["markdown-code"] = content(colors.success, success) + tokens["markdown-block-quote"] = content(colors.warning, warning) + tokens["markdown-emph"] = content(colors.warning, warning) + tokens["markdown-strong"] = content(colors.accent, accent) + tokens["markdown-horizontal-rule"] = tokens["border-base"] + tokens["markdown-list-item"] = content(colors.interactive, interactive) + tokens["markdown-list-enumeration"] = content(colors.info, info) + tokens["markdown-image"] = content(colors.interactive, interactive) + tokens["markdown-image-text"] = content(colors.info, info) + tokens["markdown-code-block"] = tokens["text-base"] + } + } + + if (!colors.compact) { + tokens["syntax-comment"] = "var(--text-weak)" + tokens["syntax-regexp"] = "var(--text-base)" + tokens["syntax-string"] = isDark ? "#00ceb9" : "#006656" + tokens["syntax-keyword"] = "var(--text-weak)" + tokens["syntax-primitive"] = isDark ? "#ffba92" : "#fb4804" + tokens["syntax-operator"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-variable"] = "var(--text-strong)" + tokens["syntax-property"] = isDark ? "#ff9ae2" : "#ed6dc8" + tokens["syntax-type"] = isDark ? "#ecf58c" : "#596600" + tokens["syntax-constant"] = isDark ? "#93e9f6" : "#007b80" + tokens["syntax-punctuation"] = isDark ? "var(--text-weak)" : "var(--text-base)" + tokens["syntax-object"] = "var(--text-strong)" + tokens["syntax-success"] = success[9] + tokens["syntax-warning"] = amber[9] + tokens["syntax-critical"] = error[9] + tokens["syntax-info"] = isDark ? "#93e9f6" : "#0092a8" + tokens["syntax-diff-add"] = diffAdd[10] + tokens["syntax-diff-delete"] = diffDelete[10] + tokens["syntax-diff-unknown"] = "#ff0000" + + tokens["markdown-heading"] = isDark ? "#9d7cd8" : "#d68c27" + tokens["markdown-text"] = isDark ? "#eeeeee" : "#1a1a1a" + tokens["markdown-link"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-link-text"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-code"] = isDark ? "#7fd88f" : "#3d9a57" + tokens["markdown-block-quote"] = isDark ? "#e5c07b" : "#b0851f" + tokens["markdown-emph"] = isDark ? "#e5c07b" : "#b0851f" + tokens["markdown-strong"] = isDark ? "#f5a742" : "#d68c27" + tokens["markdown-horizontal-rule"] = isDark ? "#808080" : "#8a8a8a" + tokens["markdown-list-item"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-list-enumeration"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-image"] = isDark ? "#fab283" : "#3b7dd8" + tokens["markdown-image-text"] = isDark ? "#56b6c2" : "#318795" + tokens["markdown-code-block"] = isDark ? "#eeeeee" : "#1a1a1a" + } tokens["avatar-background-pink"] = isDark ? "#501b3f" : "#feeef8" tokens["avatar-background-mint"] = isDark ? "#033a34" : "#e1fbf4" @@ -289,13 +584,101 @@ export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): Res tokens[key] = value } + if (hasInk && "text-weak" in overrides && !("text-weaker" in overrides)) { + const weak = tokens["text-weak"] + if (weak.startsWith("#")) { + tokens["text-weaker"] = shift(weak as HexColor, { l: isDark ? -0.12 : 0.12, c: 0.75 }) + } else { + tokens["text-weaker"] = weak + } + } + + if (colors.compact && hasInk) { + if (!("markdown-text" in overrides)) { + tokens["markdown-text"] = tokens["text-base"] + } + if (!("markdown-code-block" in overrides)) { + tokens["markdown-code-block"] = tokens["text-base"] + } + } + + if (!("text-stronger" in overrides)) { + tokens["text-stronger"] = tokens["text-strong"] + } + return tokens } +interface ThemeColors { + compact: boolean + neutral: HexColor + ink?: HexColor + primary: HexColor + accent: HexColor + success: HexColor + warning: HexColor + error: HexColor + info: HexColor + interactive: HexColor + diffAdd?: HexColor + diffDelete?: HexColor +} + +function getColors(variant: ThemeVariant): ThemeColors { + const input = variant as { palette?: unknown; seeds?: unknown } + if (input.palette && input.seeds) { + throw new Error("Theme variant cannot define both `palette` and `seeds`") + } + + if (variant.palette) { + return { + compact: true, + neutral: variant.palette.neutral, + ink: variant.palette.ink, + primary: variant.palette.primary, + accent: variant.palette.accent ?? variant.palette.info, + success: variant.palette.success, + warning: variant.palette.warning, + error: variant.palette.error, + info: variant.palette.info, + interactive: variant.palette.interactive ?? variant.palette.primary, + diffAdd: variant.palette.diffAdd, + diffDelete: variant.palette.diffDelete, + } + } + + if (variant.seeds) { + return { + compact: false, + neutral: variant.seeds.neutral, + ink: undefined, + primary: variant.seeds.primary, + accent: variant.seeds.info, + success: variant.seeds.success, + warning: variant.seeds.warning, + error: variant.seeds.error, + info: variant.seeds.info, + interactive: variant.seeds.interactive, + diffAdd: variant.seeds.diffAdd, + diffDelete: variant.seeds.diffDelete, + } + } + + throw new Error("Theme variant requires `palette` or `seeds`") +} + +function generateNeutralOverlayScale(neutralScale: HexColor[], isDark: boolean): ColorValue[] { + const alphas = isDark + ? [0, 0.034, 0.063, 0.084, 0.109, 0.138, 0.181, 0.266, 0.404, 0.468, 0.603, 0.928] + : [0.014, 0.034, 0.055, 0.075, 0.096, 0.118, 0.151, 0.232, 0.453, 0.492, 0.574, 0.915] + const color = (isDark ? "#ffffff" : "#000000") as HexColor + return alphas.map((alpha) => withAlpha(color, alpha) as ColorValue) +} + function generateNeutralAlphaScale(neutralScale: HexColor[], isDark: boolean): HexColor[] { const alphas = isDark - ? [0.02, 0.04, 0.08, 0.12, 0.16, 0.2, 0.26, 0.36, 0.44, 0.52, 0.72, 0.94] - : [0.01, 0.03, 0.06, 0.09, 0.12, 0.15, 0.2, 0.27, 0.46, 0.61, 0.5, 0.87] + ? [0.024, 0.048, 0.088, 0.128, 0.17, 0.215, 0.275, 0.38, 0.46, 0.54, 0.74, 0.95] + : [0.014, 0.034, 0.066, 0.098, 0.128, 0.158, 0.208, 0.282, 0.47, 0.625, 0.515, 0.88] return neutralScale.map((hex, i) => { const baseOklch = hexToOklch(hex) @@ -307,6 +690,11 @@ function generateNeutralAlphaScale(neutralScale: HexColor[], isDark: boolean): H }) } +function getHex(value: ColorValue | undefined): HexColor | undefined { + if (!value?.startsWith("#")) return + return value as HexColor +} + export function resolveTheme(theme: DesktopTheme): { light: ResolvedTheme; dark: ResolvedTheme } { return { light: resolveThemeVariant(theme.light, false), diff --git a/packages/ui/src/theme/themes/aura.json b/packages/ui/src/theme/themes/aura.json index 874939fd4d..e65eb4b0aa 100644 --- a/packages/ui/src/theme/themes/aura.json +++ b/packages/ui/src/theme/themes/aura.json @@ -3,129 +3,37 @@ "name": "Aura", "id": "aura", "light": { - "seeds": { + "palette": { "neutral": "#f5f0ff", + "ink": "#2d2640", "primary": "#a277ff", + "accent": "#d94f4f", "success": "#40bf7a", "warning": "#d9a24a", "error": "#d94f4f", "info": "#5bb8d9", - "interactive": "#a277ff", "diffAdd": "#b3e6cc", "diffDelete": "#f5b3b3" }, "overrides": { - "background-base": "#f5f0ff", - "background-weak": "#efe8fc", - "background-strong": "#faf7ff", - "background-stronger": "#fdfcff", - "border-weak-base": "#e0d6f2", - "border-weak-hover": "#d5c9eb", - "border-weak-active": "#cbbee3", - "border-weak-selected": "#c0b3dc", - "border-weak-disabled": "#f9f6ff", - "border-weak-focus": "#c5b8df", - "border-base": "#b5a6d4", - "border-hover": "#aa99cc", - "border-active": "#9f8dc4", - "border-selected": "#9480bc", - "border-disabled": "#ede7f9", - "border-focus": "#a593c8", - "border-strong-base": "#8068a8", - "border-strong-hover": "#735a9c", - "border-strong-active": "#664d90", - "border-strong-selected": "#5a4184", - "border-strong-disabled": "#d4c8ed", - "border-strong-focus": "#6d5396", - "surface-diff-add-base": "#e8f5ed", - "surface-diff-delete-base": "#fae8e8", - "surface-diff-hidden-base": "#e8e4f5", - "text-base": "#2d2640", - "text-weak": "#5c5270", - "text-strong": "#15101f", - "syntax-string": "#40bf7a", - "syntax-primitive": "#d94f4f", - "syntax-property": "#a277ff", - "syntax-type": "#d9a24a", - "syntax-constant": "#5bb8d9", - "syntax-info": "#5bb8d9", - "markdown-heading": "#a277ff", - "markdown-text": "#2d2640", - "markdown-link": "#c17ac8", - "markdown-link-text": "#a277ff", - "markdown-code": "#40bf7a", - "markdown-block-quote": "#6d6d6d", - "markdown-emph": "#d9a24a", - "markdown-strong": "#a277ff", - "markdown-horizontal-rule": "#d4c8ed", - "markdown-list-item": "#a277ff", - "markdown-list-enumeration": "#a277ff", - "markdown-image": "#c17ac8", - "markdown-image-text": "#a277ff", - "markdown-code-block": "#5bb8d9" + "syntax-keyword": "#7b5ae0" } }, "dark": { - "seeds": { + "palette": { "neutral": "#15141b", + "ink": "#edecee", "primary": "#a277ff", + "accent": "#ff6767", "success": "#61ffca", "warning": "#ffca85", "error": "#ff6767", "info": "#82e2ff", - "interactive": "#a277ff", "diffAdd": "#61ffca", "diffDelete": "#ff6767" }, "overrides": { - "background-base": "#15141b", - "background-weak": "#1a1921", - "background-strong": "#121118", - "background-stronger": "#0f0e14", - "border-weak-base": "#2d2b38", - "border-weak-hover": "#332f42", - "border-weak-active": "#38354c", - "border-weak-selected": "#3e3a56", - "border-weak-disabled": "#1a1921", - "border-weak-focus": "#363350", - "border-base": "#433f5a", - "border-hover": "#4a4565", - "border-active": "#514c70", - "border-selected": "#58527b", - "border-disabled": "#1f1e28", - "border-focus": "#4e496c", - "border-strong-base": "#635c8a", - "border-strong-hover": "#6d6597", - "border-strong-active": "#776fa4", - "border-strong-selected": "#8179b1", - "border-strong-disabled": "#2a283a", - "border-strong-focus": "#716a9e", - "surface-diff-add-base": "#162620", - "surface-diff-delete-base": "#26161a", - "surface-diff-hidden-base": "#1e1d2a", - "text-base": "#edecee", - "text-weak": "#6d6d6d", - "text-strong": "#ffffff", - "syntax-string": "#61ffca", - "syntax-primitive": "#ff6767", - "syntax-property": "#a277ff", - "syntax-type": "#ffca85", - "syntax-constant": "#82e2ff", - "syntax-info": "#82e2ff", - "markdown-heading": "#a277ff", - "markdown-text": "#edecee", - "markdown-link": "#f694ff", - "markdown-link-text": "#a277ff", - "markdown-code": "#61ffca", - "markdown-block-quote": "#6d6d6d", - "markdown-emph": "#ffca85", - "markdown-strong": "#a277ff", - "markdown-horizontal-rule": "#2d2b38", - "markdown-list-item": "#a277ff", - "markdown-list-enumeration": "#a277ff", - "markdown-image": "#f694ff", - "markdown-image-text": "#a277ff", - "markdown-code-block": "#edecee" + "syntax-keyword": "#a277ff" } } } diff --git a/packages/ui/src/theme/themes/ayu.json b/packages/ui/src/theme/themes/ayu.json index eac9e0491f..f459489035 100644 --- a/packages/ui/src/theme/themes/ayu.json +++ b/packages/ui/src/theme/themes/ayu.json @@ -3,131 +3,37 @@ "name": "Ayu", "id": "ayu", "light": { - "seeds": { + "palette": { "neutral": "#fdfaf4", + "ink": "#4f5964", "primary": "#4aa8c8", + "accent": "#ef7d71", "success": "#5fb978", "warning": "#ea9f41", "error": "#e6656a", "info": "#2f9bce", - "interactive": "#4aa8c8", "diffAdd": "#b1d780", "diffDelete": "#e6656a" }, "overrides": { - "background-base": "#fdfaf4", - "background-weak": "#fcf9f3", - "background-strong": "#fbf8f2", - "background-stronger": "#faf7f1", - "surface-raised-base-hover": "#f4f0e9", - "border-weak-base": "#e6ddcf", - "border-weak-hover": "#dcd3c5", - "border-weak-active": "#d1c9ba", - "border-weak-selected": "#c6bfaf", - "border-weak-disabled": "#f7f0e6", - "border-weak-focus": "#cbc4b6", - "border-base": "#bfb3a3", - "border-hover": "#b4a898", - "border-active": "#a99e8e", - "border-selected": "#9e9383", - "border-disabled": "#efe5d8", - "border-focus": "#b09f8f", - "border-strong-base": "#837765", - "border-strong-hover": "#7a6f5f", - "border-strong-active": "#716655", - "border-strong-selected": "#685e4e", - "border-strong-disabled": "#d8cabc", - "border-strong-focus": "#766b5c", - "surface-diff-add-base": "#eef5e4", - "surface-diff-delete-base": "#fde5e5", - "surface-diff-hidden-base": "#e3edf3", - "text-base": "#4f5964", - "text-weak": "#77818d", - "text-strong": "#1b232b", - "syntax-string": "#7fad00", - "syntax-primitive": "#ef7d71", - "syntax-property": "#4aa8c8", - "syntax-type": "#ed982e", - "syntax-constant": "#2f9bce", - "syntax-info": "#2f9bce", - "markdown-heading": "#4aa8c8", - "markdown-text": "#4f5964", - "markdown-link": "#4aa8c8", - "markdown-link-text": "#2f9bce", - "markdown-code": "#7fad00", - "markdown-block-quote": "#ed982e", - "markdown-emph": "#ed982e", - "markdown-strong": "#f07f72", - "markdown-horizontal-rule": "#d7cec0", - "markdown-list-item": "#4aa8c8", - "markdown-list-enumeration": "#2f9bce", - "markdown-image": "#4aa8c8", - "markdown-image-text": "#2f9bce", - "markdown-code-block": "#4aa8c8" + "syntax-keyword": "#ea9f41" } }, "dark": { - "seeds": { + "palette": { "neutral": "#0f1419", + "ink": "#d6dae0", "primary": "#3fb7e3", + "accent": "#f2856f", "success": "#78d05c", "warning": "#e4a75c", "error": "#f58572", "info": "#66c6f1", - "interactive": "#3fb7e3", "diffAdd": "#59c57c", "diffDelete": "#f58572" }, "overrides": { - "background-base": "#0f1419", - "background-weak": "#18222c", - "background-strong": "#0b1015", - "background-stronger": "#080c10", - "surface-raised-base-hover": "#0f1419", - "border-weak-base": "#2b3440", - "border-weak-hover": "#323c49", - "border-weak-active": "#394454", - "border-weak-selected": "#415063", - "border-weak-disabled": "#0a0e12", - "border-weak-focus": "#374453", - "border-base": "#475367", - "border-hover": "#515f75", - "border-active": "#5d6b83", - "border-selected": "#687795", - "border-disabled": "#11161d", - "border-focus": "#56647c", - "border-strong-base": "#73819b", - "border-strong-hover": "#7f8da8", - "border-strong-active": "#8b99b5", - "border-strong-selected": "#98a6c3", - "border-strong-disabled": "#1b222c", - "border-strong-focus": "#8391ad", - "surface-diff-add-base": "#132f27", - "surface-diff-delete-base": "#361d20", - "surface-diff-hidden-base": "#1b2632", - "text-base": "#d6dae0", - "text-weak": "#a3adba", - "text-strong": "#fbfbfd", - "syntax-string": "#b1c74a", - "syntax-primitive": "#f2856f", - "syntax-property": "#3fb7e3", - "syntax-type": "#e4a75c", - "syntax-constant": "#66c6f1", - "syntax-info": "#66c6f1", - "markdown-heading": "#3fb7e3", - "markdown-text": "#d6dae0", - "markdown-link": "#3fb7e3", - "markdown-link-text": "#66c6f1", - "markdown-code": "#b1c74a", - "markdown-block-quote": "#e4a75c", - "markdown-emph": "#e4a75c", - "markdown-strong": "#f2856f", - "markdown-horizontal-rule": "#2b3542", - "markdown-list-item": "#3fb7e3", - "markdown-list-enumeration": "#66c6f1", - "markdown-image": "#3fb7e3", - "markdown-image-text": "#66c6f1", - "markdown-code-block": "#d6dae0" + "syntax-keyword": "#ffad66" } } } diff --git a/packages/ui/src/theme/themes/carbonfox.json b/packages/ui/src/theme/themes/carbonfox.json index e2fa20d803..54e55cdeae 100644 --- a/packages/ui/src/theme/themes/carbonfox.json +++ b/packages/ui/src/theme/themes/carbonfox.json @@ -3,9 +3,11 @@ "name": "Carbonfox", "id": "carbonfox", "light": { - "seeds": { + "palette": { "neutral": "#8e8e8e", + "ink": "#161616", "primary": "#0072c3", + "accent": "#da1e28", "success": "#198038", "warning": "#f1c21b", "error": "#da1e28", @@ -15,44 +17,15 @@ "diffDelete": "#da1e28" }, "overrides": { - "background-base": "#ffffff", - "background-weak": "#f4f4f4", - "background-strong": "#e8e8e8", - "background-stronger": "#dcdcdc", - "surface-raised-strong": "#ffffff", - "surface-raised-stronger": "#ffffff", - "surface-float-base": "#161616", - "surface-float-base-hover": "#262626", - "text-base": "#161616", - "text-weak": "#525252", - "text-strong": "#000000", - "syntax-string": "#198038", - "syntax-primitive": "#da1e28", - "syntax-property": "#0043ce", - "syntax-type": "#007d79", - "syntax-constant": "#6929c4", - "syntax-keyword": "#525252", - "syntax-info": "#0043ce", - "markdown-heading": "#0043ce", - "markdown-text": "#161616", - "markdown-link": "#0043ce", - "markdown-link-text": "#0072c3", - "markdown-code": "#198038", - "markdown-block-quote": "#525252", - "markdown-emph": "#6929c4", - "markdown-strong": "#161616", - "markdown-horizontal-rule": "#c6c6c6", - "markdown-list-item": "#0072c3", - "markdown-list-enumeration": "#0072c3", - "markdown-image": "#0043ce", - "markdown-image-text": "#0072c3", - "markdown-code-block": "#393939" + "syntax-keyword": "#8a3ffc" } }, "dark": { - "seeds": { + "palette": { "neutral": "#393939", + "ink": "#f2f4f8", "primary": "#33b1ff", + "accent": "#ff8389", "success": "#42be65", "warning": "#f1c21b", "error": "#ff8389", @@ -62,61 +35,7 @@ "diffDelete": "#ff8389" }, "overrides": { - "background-base": "#161616", - "background-weak": "#262626", - "background-strong": "#0d0d0d", - "background-stronger": "#000000", - "surface-raised-base": "#1c1c1c", - "surface-raised-base-hover": "#262626", - "surface-raised-strong": "#262626", - "surface-raised-strong-hover": "#303030", - "surface-raised-stronger": "#303030", - "surface-raised-stronger-hover": "#393939", - "surface-raised-stronger-non-alpha": "#303030", - "surface-float-base": "#0d0d0d", - "surface-float-base-hover": "#1a1a1a", - "surface-inset-base": "#0d0d0d", - "surface-inset-strong": "#000000", - "surface-base": "#1e1e1e", - "surface-base-hover": "#262626", - "surface-diff-add-base": "#0e3a22", - "surface-diff-delete-base": "#4d1a1f", - "input-base": "#262626", - "input-hover": "#303030", - "button-secondary-base": "#393939", - "button-secondary-hover": "#4c4c4c", - "border-weak-base": "#393939", - "border-weak-hover": "#4c4c4c", - "border-base": "#525252", - "border-hover": "#636363", - "border-strong-base": "#6f6f6f", - "text-base": "#f2f4f8", - "text-weak": "#8d8d8d", - "text-weaker": "#6f6f6f", - "text-strong": "#ffffff", - "icon-base": "#8d8d8d", - "icon-weak-base": "#6f6f6f", - "syntax-string": "#42be65", - "syntax-primitive": "#ff8389", - "syntax-property": "#78a9ff", - "syntax-type": "#08bdba", - "syntax-constant": "#be95ff", - "syntax-keyword": "#8d8d8d", - "syntax-info": "#78a9ff", - "markdown-heading": "#82cfff", - "markdown-text": "#f2f4f8", - "markdown-link": "#78a9ff", - "markdown-link-text": "#33b1ff", - "markdown-code": "#42be65", - "markdown-block-quote": "#8d8d8d", - "markdown-emph": "#be95ff", - "markdown-strong": "#ffffff", - "markdown-horizontal-rule": "#393939", - "markdown-list-item": "#33b1ff", - "markdown-list-enumeration": "#33b1ff", - "markdown-image": "#78a9ff", - "markdown-image-text": "#33b1ff", - "markdown-code-block": "#c6c6c6" + "syntax-keyword": "#be95ff" } } } diff --git a/packages/ui/src/theme/themes/catppuccin.json b/packages/ui/src/theme/themes/catppuccin.json index 2a32df0984..66fd37e26b 100644 --- a/packages/ui/src/theme/themes/catppuccin.json +++ b/packages/ui/src/theme/themes/catppuccin.json @@ -3,129 +3,39 @@ "name": "Catppuccin", "id": "catppuccin", "light": { - "seeds": { + "palette": { "neutral": "#f5e0dc", + "ink": "#4c4f69", "primary": "#7287fd", + "accent": "#d20f39", "success": "#40a02b", "warning": "#df8e1d", "error": "#d20f39", "info": "#04a5e5", - "interactive": "#7287fd", "diffAdd": "#a6d189", "diffDelete": "#e78284" }, "overrides": { - "background-base": "#f5e0dc", - "background-weak": "#f2d8d4", - "background-strong": "#f9e8e4", - "background-stronger": "#fdeeee", - "border-weak-base": "#e0cfd3", - "border-weak-hover": "#d6c4c8", - "border-weak-active": "#cdb9be", - "border-weak-selected": "#c2aeb4", - "border-weak-disabled": "#fbeff2", - "border-weak-focus": "#c7b4ba", - "border-base": "#bca6b2", - "border-hover": "#b19ca8", - "border-active": "#a6929e", - "border-selected": "#9a8894", - "border-disabled": "#f3e4e7", - "border-focus": "#ab97a1", - "border-strong-base": "#83677f", - "border-strong-hover": "#775b73", - "border-strong-active": "#6b5068", - "border-strong-selected": "#5f465d", - "border-strong-disabled": "#d9c5cf", - "border-strong-focus": "#714f66", - "surface-diff-add-base": "#edf5e6", - "surface-diff-delete-base": "#fde1e3", - "surface-diff-hidden-base": "#e4e2f6", - "text-base": "#4c4f69", - "text-weak": "#6c6f85", - "text-strong": "#1f1f2a", - "syntax-string": "#40a02b", - "syntax-primitive": "#d20f39", - "syntax-property": "#7287fd", - "syntax-type": "#df8e1d", - "syntax-constant": "#04a5e5", - "syntax-info": "#04a5e5", - "markdown-heading": "#7287fd", - "markdown-text": "#4c4f69", - "markdown-link": "#7287fd", - "markdown-link-text": "#04a5e5", - "markdown-code": "#40a02b", - "markdown-block-quote": "#df8e1d", - "markdown-emph": "#df8e1d", - "markdown-strong": "#d20f39", - "markdown-horizontal-rule": "#d4c5cf", - "markdown-list-item": "#7287fd", - "markdown-list-enumeration": "#04a5e5", - "markdown-image": "#7287fd", - "markdown-image-text": "#04a5e5", - "markdown-code-block": "#7287fd" + "syntax-keyword": "#8839ef", + "syntax-primitive": "#fe640b" } }, "dark": { - "seeds": { + "palette": { "neutral": "#1e1e2e", + "ink": "#cdd6f4", "primary": "#b4befe", + "accent": "#f38ba8", "success": "#a6d189", "warning": "#f4b8e4", "error": "#f38ba8", "info": "#89dceb", - "interactive": "#b4befe", "diffAdd": "#94e2d5", "diffDelete": "#f38ba8" }, "overrides": { - "background-base": "#1e1e2e", - "background-weak": "#211f31", - "background-strong": "#1c1c29", - "background-stronger": "#191926", - "border-weak-base": "#35324a", - "border-weak-hover": "#393655", - "border-weak-active": "#403c61", - "border-weak-selected": "#47436d", - "border-weak-disabled": "#141426", - "border-weak-focus": "#3d3a63", - "border-base": "#4a4763", - "border-hover": "#524f70", - "border-active": "#5a577d", - "border-selected": "#625f8a", - "border-disabled": "#1b1a2c", - "border-focus": "#575379", - "border-strong-base": "#6e6a8c", - "border-strong-hover": "#787497", - "border-strong-active": "#8380a2", - "border-strong-selected": "#8d8bad", - "border-strong-disabled": "#232237", - "border-strong-focus": "#7b779b", - "surface-diff-add-base": "#1d2c30", - "surface-diff-delete-base": "#2c1f2a", - "surface-diff-hidden-base": "#232538", - "text-base": "#cdd6f4", - "text-weak": "#a6adc8", - "text-strong": "#f4f2ff", - "syntax-string": "#a6e3a1", - "syntax-primitive": "#f38ba8", - "syntax-property": "#b4befe", - "syntax-type": "#f9e2af", - "syntax-constant": "#89dceb", - "syntax-info": "#89dceb", - "markdown-heading": "#b4befe", - "markdown-text": "#cdd6f4", - "markdown-link": "#b4befe", - "markdown-link-text": "#89dceb", - "markdown-code": "#a6e3a1", - "markdown-block-quote": "#f9e2af", - "markdown-emph": "#f9e2af", - "markdown-strong": "#f38ba8", - "markdown-horizontal-rule": "#2e2d45", - "markdown-list-item": "#b4befe", - "markdown-list-enumeration": "#89dceb", - "markdown-image": "#b4befe", - "markdown-image-text": "#89dceb", - "markdown-code-block": "#cdd6f4" + "syntax-keyword": "#cba6f7", + "syntax-primitive": "#fab387" } } } diff --git a/packages/ui/src/theme/themes/dracula.json b/packages/ui/src/theme/themes/dracula.json index 696f1060c7..495042ca7b 100644 --- a/packages/ui/src/theme/themes/dracula.json +++ b/packages/ui/src/theme/themes/dracula.json @@ -3,129 +3,41 @@ "name": "Dracula", "id": "dracula", "light": { - "seeds": { + "palette": { "neutral": "#f8f8f2", + "ink": "#1f1f2f", "primary": "#7c6bf5", + "accent": "#d16090", "success": "#2fbf71", "warning": "#f7a14d", "error": "#d9536f", "info": "#1d7fc5", - "interactive": "#7c6bf5", "diffAdd": "#9fe3b3", "diffDelete": "#f8a1b8" }, "overrides": { - "background-base": "#f8f8f2", - "background-weak": "#f1f2ed", - "background-strong": "#f6f6f1", - "background-stronger": "#f2f2ec", - "border-weak-base": "#e2e3da", - "border-weak-hover": "#d8d9d0", - "border-weak-active": "#cfd0c7", - "border-weak-selected": "#c4c6bc", - "border-weak-disabled": "#eceee3", - "border-weak-focus": "#c9cabf", - "border-base": "#c4c6ba", - "border-hover": "#b8baae", - "border-active": "#abada3", - "border-selected": "#979a90", - "border-disabled": "#e5e7dd", - "border-focus": "#b0b2a7", - "border-strong-base": "#9fa293", - "border-strong-hover": "#8e9185", - "border-strong-active": "#7e8176", - "border-strong-selected": "#6f7268", - "border-strong-disabled": "#c7c9be", - "border-strong-focus": "#878b7f", - "surface-diff-add-base": "#e4f5e6", - "surface-diff-delete-base": "#fae4eb", - "surface-diff-hidden-base": "#dedfe9", - "text-base": "#1f1f2f", - "text-weak": "#52526b", - "text-strong": "#05040c", - "syntax-string": "#2fbf71", - "syntax-primitive": "#d16090", - "syntax-property": "#7c6bf5", - "syntax-type": "#f7a14d", - "syntax-constant": "#1d7fc5", - "syntax-info": "#1d7fc5", - "markdown-heading": "#7c6bf5", - "markdown-text": "#1f1f2f", - "markdown-link": "#7c6bf5", - "markdown-link-text": "#1d7fc5", - "markdown-code": "#2fbf71", - "markdown-block-quote": "#f7a14d", - "markdown-emph": "#f7a14d", - "markdown-strong": "#d16090", - "markdown-horizontal-rule": "#c3c5d4", - "markdown-list-item": "#7c6bf5", - "markdown-list-enumeration": "#1d7fc5", - "markdown-image": "#7c6bf5", - "markdown-image-text": "#1d7fc5", - "markdown-code-block": "#1d7fc5" + "syntax-keyword": "#d16090", + "syntax-string": "#596600", + "syntax-primitive": "#7c6bf5" } }, "dark": { - "seeds": { + "palette": { "neutral": "#1d1e28", + "ink": "#f8f8f2", "primary": "#bd93f9", + "accent": "#ff79c6", "success": "#50fa7b", "warning": "#ffb86c", "error": "#ff5555", "info": "#8be9fd", - "interactive": "#bd93f9", "diffAdd": "#2fb27d", "diffDelete": "#ff6b81" }, "overrides": { - "background-base": "#14151f", - "background-weak": "#181926", - "background-strong": "#161722", - "background-stronger": "#191a26", - "border-weak-base": "#2d2f3c", - "border-weak-hover": "#303244", - "border-weak-active": "#35364c", - "border-weak-selected": "#3b3d55", - "border-weak-disabled": "#1e1f2b", - "border-weak-focus": "#383a50", - "border-base": "#3f415a", - "border-hover": "#464967", - "border-active": "#4d5073", - "border-selected": "#55587f", - "border-disabled": "#272834", - "border-focus": "#4a4d6d", - "border-strong-base": "#606488", - "border-strong-hover": "#6a6e96", - "border-strong-active": "#7378a3", - "border-strong-selected": "#7d82b1", - "border-strong-disabled": "#343649", - "border-strong-focus": "#6f739c", - "surface-diff-add-base": "#1f2a2f", - "surface-diff-delete-base": "#2d1f27", - "surface-diff-hidden-base": "#24253a", - "text-base": "#f8f8f2", - "text-weak": "#b6b9e4", - "text-strong": "#ffffff", - "syntax-string": "#50fa7b", - "syntax-primitive": "#ff79c6", - "syntax-property": "#bd93f9", - "syntax-type": "#ffb86c", - "syntax-constant": "#8be9fd", - "syntax-info": "#8be9fd", - "markdown-heading": "#bd93f9", - "markdown-text": "#f8f8f2", - "markdown-link": "#bd93f9", - "markdown-link-text": "#8be9fd", - "markdown-code": "#50fa7b", - "markdown-block-quote": "#ffb86c", - "markdown-emph": "#ffb86c", - "markdown-strong": "#ff79c6", - "markdown-horizontal-rule": "#44475a", - "markdown-list-item": "#bd93f9", - "markdown-list-enumeration": "#8be9fd", - "markdown-image": "#bd93f9", - "markdown-image-text": "#8be9fd", - "markdown-code-block": "#f8f8f2" + "syntax-keyword": "#ff79c6", + "syntax-string": "#f1fa8c", + "syntax-primitive": "#bd93f9" } } } diff --git a/packages/ui/src/theme/themes/gruvbox.json b/packages/ui/src/theme/themes/gruvbox.json index cf87ccd553..f078db2d4c 100644 --- a/packages/ui/src/theme/themes/gruvbox.json +++ b/packages/ui/src/theme/themes/gruvbox.json @@ -3,130 +3,39 @@ "name": "Gruvbox", "id": "gruvbox", "light": { - "seeds": { + "palette": { "neutral": "#fbf1c7", + "ink": "#3c3836", "primary": "#076678", + "accent": "#9d0006", "success": "#79740e", "warning": "#b57614", "error": "#9d0006", "info": "#8f3f71", - "interactive": "#076678", "diffAdd": "#79740e", "diffDelete": "#9d0006" }, "overrides": { - "background-base": "#fbf1c7", - "background-weak": "#f2e5bc", - "background-strong": "#f9f5d7", - "background-stronger": "#fdf9e8", - "surface-raised-stronger-non-alpha": "#fbfaf5", - "border-weak-base": "#d5c4a1", - "border-weak-hover": "#c9b897", - "border-weak-active": "#bdae93", - "border-weak-selected": "#b0a285", - "border-weak-disabled": "#f0e4b8", - "border-weak-focus": "#c4b590", - "border-base": "#bdae93", - "border-hover": "#b0a285", - "border-active": "#a89984", - "border-selected": "#928374", - "border-disabled": "#e5d9ad", - "border-focus": "#a89984", - "border-strong-base": "#7c6f64", - "border-strong-hover": "#6e6259", - "border-strong-active": "#665c54", - "border-strong-selected": "#5a524b", - "border-strong-disabled": "#c9bda1", - "border-strong-focus": "#665c54", - "surface-diff-add-base": "#dde3b1", - "surface-diff-delete-base": "#e8c7c3", - "surface-diff-hidden-base": "#ebdfb5", - "text-base": "#3c3836", - "text-weak": "#7c6f64", - "text-strong": "#282828", - "syntax-string": "#79740e", - "syntax-primitive": "#9d0006", - "syntax-property": "#076678", - "syntax-type": "#b57614", - "syntax-constant": "#8f3f71", - "syntax-info": "#427b58", - "markdown-heading": "#076678", - "markdown-text": "#3c3836", - "markdown-link": "#076678", - "markdown-link-text": "#427b58", - "markdown-code": "#79740e", - "markdown-block-quote": "#928374", - "markdown-emph": "#8f3f71", - "markdown-strong": "#af3a03", - "markdown-horizontal-rule": "#d5c4a1", - "markdown-list-item": "#076678", - "markdown-list-enumeration": "#427b58", - "markdown-image": "#076678", - "markdown-image-text": "#427b58", - "markdown-code-block": "#3c3836" + "syntax-keyword": "#9d0006", + "syntax-primitive": "#8f3f71" } }, "dark": { - "seeds": { + "palette": { "neutral": "#282828", + "ink": "#ebdbb2", "primary": "#83a598", + "accent": "#fb4934", "success": "#b8bb26", "warning": "#fabd2f", "error": "#fb4934", "info": "#d3869b", - "interactive": "#83a598", "diffAdd": "#b8bb26", "diffDelete": "#fb4934" }, "overrides": { - "background-base": "#282828", - "background-weak": "#32302f", - "background-strong": "#1d2021", - "background-stronger": "#141617", - "border-weak-base": "#504945", - "border-weak-hover": "#5a524b", - "border-weak-active": "#665c54", - "border-weak-selected": "#70665d", - "border-weak-disabled": "#1e1d1c", - "border-weak-focus": "#5e5650", - "border-base": "#665c54", - "border-hover": "#70665d", - "border-active": "#7c6f64", - "border-selected": "#928374", - "border-disabled": "#2a2827", - "border-focus": "#7c6f64", - "border-strong-base": "#928374", - "border-strong-hover": "#9d8e7f", - "border-strong-active": "#a89984", - "border-strong-selected": "#b3a48f", - "border-strong-disabled": "#3c3836", - "border-strong-focus": "#a89984", - "surface-diff-add-base": "#2a3325", - "surface-diff-delete-base": "#3c2222", - "surface-diff-hidden-base": "#32302f", - "text-base": "#ebdbb2", - "text-weak": "#a89984", - "text-strong": "#fbf1c7", - "syntax-string": "#b8bb26", - "syntax-primitive": "#fb4934", - "syntax-property": "#83a598", - "syntax-type": "#fabd2f", - "syntax-constant": "#d3869b", - "syntax-info": "#8ec07c", - "markdown-heading": "#83a598", - "markdown-text": "#ebdbb2", - "markdown-link": "#83a598", - "markdown-link-text": "#8ec07c", - "markdown-code": "#b8bb26", - "markdown-block-quote": "#928374", - "markdown-emph": "#d3869b", - "markdown-strong": "#fe8019", - "markdown-horizontal-rule": "#504945", - "markdown-list-item": "#83a598", - "markdown-list-enumeration": "#8ec07c", - "markdown-image": "#83a598", - "markdown-image-text": "#8ec07c", - "markdown-code-block": "#ebdbb2" + "syntax-keyword": "#fb4934", + "syntax-primitive": "#d3869b" } } } diff --git a/packages/ui/src/theme/themes/monokai.json b/packages/ui/src/theme/themes/monokai.json index d49846ddb3..3a2656b6ea 100644 --- a/packages/ui/src/theme/themes/monokai.json +++ b/packages/ui/src/theme/themes/monokai.json @@ -3,129 +3,41 @@ "name": "Monokai", "id": "monokai", "light": { - "seeds": { + "palette": { "neutral": "#fdf8ec", + "ink": "#292318", "primary": "#bf7bff", + "accent": "#d9487c", "success": "#4fb54b", "warning": "#f1a948", "error": "#e54b4b", "info": "#2d9ad7", - "interactive": "#bf7bff", "diffAdd": "#bfe7a3", "diffDelete": "#f6a3ae" }, "overrides": { - "background-base": "#fdf8ec", - "background-weak": "#f8f2e6", - "background-strong": "#fbf5e8", - "background-stronger": "#f7efdd", - "border-weak-base": "#e9e0cf", - "border-weak-hover": "#dfd5c3", - "border-weak-active": "#d5cab7", - "border-weak-selected": "#cabfad", - "border-weak-disabled": "#f3ebdd", - "border-weak-focus": "#d0c2b1", - "border-base": "#c7b9a5", - "border-hover": "#bcae98", - "border-active": "#b0a28c", - "border-selected": "#a49781", - "border-disabled": "#efe5d6", - "border-focus": "#b6a893", - "border-strong-base": "#998b76", - "border-strong-hover": "#8a7c67", - "border-strong-active": "#7a6d58", - "border-strong-selected": "#6c604c", - "border-strong-disabled": "#d7cabc", - "border-strong-focus": "#82745f", - "surface-diff-add-base": "#e8f7e1", - "surface-diff-delete-base": "#fde5e4", - "surface-diff-hidden-base": "#e9e0d0", - "text-base": "#292318", - "text-weak": "#6d5c40", - "text-strong": "#1c150c", - "syntax-string": "#4fb54b", - "syntax-primitive": "#d9487c", - "syntax-property": "#bf7bff", - "syntax-type": "#f1a948", - "syntax-constant": "#2d9ad7", - "syntax-info": "#2d9ad7", - "markdown-heading": "#bf7bff", - "markdown-text": "#292318", - "markdown-link": "#bf7bff", - "markdown-link-text": "#2d9ad7", - "markdown-code": "#4fb54b", - "markdown-block-quote": "#f1a948", - "markdown-emph": "#f1a948", - "markdown-strong": "#d9487c", - "markdown-horizontal-rule": "#cdbdab", - "markdown-list-item": "#bf7bff", - "markdown-list-enumeration": "#2d9ad7", - "markdown-image": "#bf7bff", - "markdown-image-text": "#2d9ad7", - "markdown-code-block": "#2d9ad7" + "syntax-keyword": "#d9487c", + "syntax-string": "#8a6500", + "syntax-primitive": "#bf7bff" } }, "dark": { - "seeds": { + "palette": { "neutral": "#272822", + "ink": "#f8f8f2", "primary": "#ae81ff", + "accent": "#f92672", "success": "#a6e22e", "warning": "#fd971f", "error": "#f92672", "info": "#66d9ef", - "interactive": "#ae81ff", "diffAdd": "#4d7f2a", "diffDelete": "#f4477c" }, "overrides": { - "background-base": "#23241e", - "background-weak": "#27281f", - "background-strong": "#25261f", - "background-stronger": "#292a23", - "border-weak-base": "#343528", - "border-weak-hover": "#393a2d", - "border-weak-active": "#3f4033", - "border-weak-selected": "#454639", - "border-weak-disabled": "#1d1e16", - "border-weak-focus": "#414235", - "border-base": "#494a3a", - "border-hover": "#50523f", - "border-active": "#585a45", - "border-selected": "#60624b", - "border-disabled": "#23241b", - "border-focus": "#555741", - "border-strong-base": "#6a6c55", - "border-strong-hover": "#73755d", - "border-strong-active": "#7d7f66", - "border-strong-selected": "#878970", - "border-strong-disabled": "#2c2d23", - "border-strong-focus": "#7a7c63", - "surface-diff-add-base": "#1e2a1d", - "surface-diff-delete-base": "#301c24", - "surface-diff-hidden-base": "#2f2f24", - "text-base": "#f8f8f2", - "text-weak": "#c5c5c0", - "text-strong": "#ffffff", - "syntax-string": "#a6e22e", - "syntax-primitive": "#f92672", - "syntax-property": "#ae81ff", - "syntax-type": "#fd971f", - "syntax-constant": "#66d9ef", - "syntax-info": "#66d9ef", - "markdown-heading": "#ae81ff", - "markdown-text": "#f8f8f2", - "markdown-link": "#ae81ff", - "markdown-link-text": "#66d9ef", - "markdown-code": "#a6e22e", - "markdown-block-quote": "#fd971f", - "markdown-emph": "#fd971f", - "markdown-strong": "#f92672", - "markdown-horizontal-rule": "#3b3c34", - "markdown-list-item": "#ae81ff", - "markdown-list-enumeration": "#66d9ef", - "markdown-image": "#ae81ff", - "markdown-image-text": "#66d9ef", - "markdown-code-block": "#f8f8f2" + "syntax-keyword": "#f92672", + "syntax-string": "#e6db74", + "syntax-primitive": "#ae81ff" } } } diff --git a/packages/ui/src/theme/themes/nightowl.json b/packages/ui/src/theme/themes/nightowl.json index 5b0331e5fd..d6b4d4dad2 100644 --- a/packages/ui/src/theme/themes/nightowl.json +++ b/packages/ui/src/theme/themes/nightowl.json @@ -3,129 +3,40 @@ "name": "Night Owl", "id": "nightowl", "light": { - "seeds": { + "palette": { "neutral": "#f0f0f0", + "ink": "#403f53", "primary": "#4876d6", + "accent": "#aa0982", "success": "#2aa298", "warning": "#c96765", "error": "#de3d3b", "info": "#4876d6", - "interactive": "#4876d6", "diffAdd": "#2aa298", "diffDelete": "#de3d3b" }, "overrides": { - "background-base": "#fbfbfb", - "background-weak": "#f0f0f0", - "background-strong": "#ffffff", - "background-stronger": "#ffffff", - "border-weak-base": "#d9d9d9", - "border-weak-hover": "#cccccc", - "border-weak-active": "#bfbfbf", - "border-weak-selected": "#4876d6", - "border-weak-disabled": "#e6e6e6", - "border-weak-focus": "#4876d6", - "border-base": "#c0c0c0", - "border-hover": "#b3b3b3", - "border-active": "#a6a6a6", - "border-selected": "#4876d6", - "border-disabled": "#d9d9d9", - "border-focus": "#4876d6", - "border-strong-base": "#90a7b2", - "border-strong-hover": "#7d9aa6", - "border-strong-active": "#6a8d9a", - "border-strong-selected": "#4876d6", - "border-strong-disabled": "#c0c0c0", - "border-strong-focus": "#4876d6", - "surface-diff-add-base": "#eaf8f6", - "surface-diff-delete-base": "#fbe9e9", - "surface-diff-hidden-base": "#e8f0fc", - "text-base": "#403f53", - "text-weak": "#7a8181", - "text-strong": "#1a1a1a", - "syntax-string": "#c96765", - "syntax-primitive": "#aa0982", - "syntax-property": "#4876d6", - "syntax-type": "#994cc3", - "syntax-constant": "#2aa298", - "syntax-info": "#4876d6", - "markdown-heading": "#4876d6", - "markdown-text": "#403f53", - "markdown-link": "#4876d6", - "markdown-link-text": "#2aa298", - "markdown-code": "#2aa298", - "markdown-block-quote": "#7a8181", - "markdown-emph": "#994cc3", - "markdown-strong": "#c96765", - "markdown-horizontal-rule": "#90a7b2", - "markdown-list-item": "#4876d6", - "markdown-list-enumeration": "#2aa298", - "markdown-image": "#4876d6", - "markdown-image-text": "#2aa298", - "markdown-code-block": "#403f53" + "syntax-keyword": "#994cc3" } }, "dark": { - "seeds": { + "palette": { "neutral": "#011627", + "ink": "#d6deeb", "primary": "#82aaff", + "accent": "#f78c6c", "success": "#c5e478", "warning": "#ecc48d", "error": "#ef5350", "info": "#82aaff", - "interactive": "#82aaff", "diffAdd": "#c5e478", "diffDelete": "#ef5350" }, "overrides": { - "background-base": "#011627", - "background-weak": "#0b253a", - "background-strong": "#001122", - "background-stronger": "#000c17", - "border-weak-base": "#1d3b53", - "border-weak-hover": "#234561", - "border-weak-active": "#2a506f", - "border-weak-selected": "#82aaff", - "border-weak-disabled": "#0f2132", - "border-weak-focus": "#82aaff", - "border-base": "#3a5a75", - "border-hover": "#456785", - "border-active": "#507494", - "border-selected": "#82aaff", - "border-disabled": "#1a3347", - "border-focus": "#82aaff", - "border-strong-base": "#5f7e97", - "border-strong-hover": "#6e8da6", - "border-strong-active": "#7d9cb5", - "border-strong-selected": "#82aaff", - "border-strong-disabled": "#2c4a63", - "border-strong-focus": "#82aaff", - "surface-diff-add-base": "#0a2e1a", - "surface-diff-delete-base": "#2d1b1b", - "surface-diff-hidden-base": "#0b253a", - "text-base": "#d6deeb", - "text-weak": "#5f7e97", - "text-strong": "#ffffff", + "syntax-comment": "#637777", + "syntax-keyword": "#c792ea", "syntax-string": "#ecc48d", - "syntax-primitive": "#f78c6c", - "syntax-property": "#82aaff", - "syntax-type": "#c5e478", - "syntax-constant": "#7fdbca", - "syntax-info": "#82aaff", - "markdown-heading": "#82aaff", - "markdown-text": "#d6deeb", - "markdown-link": "#82aaff", - "markdown-link-text": "#7fdbca", - "markdown-code": "#c5e478", - "markdown-block-quote": "#5f7e97", - "markdown-emph": "#c792ea", - "markdown-strong": "#ecc48d", - "markdown-horizontal-rule": "#5f7e97", - "markdown-list-item": "#82aaff", - "markdown-list-enumeration": "#7fdbca", - "markdown-image": "#82aaff", - "markdown-image-text": "#7fdbca", - "markdown-code-block": "#d6deeb" + "syntax-primitive": "#f78c6c" } } } diff --git a/packages/ui/src/theme/themes/nord.json b/packages/ui/src/theme/themes/nord.json index 44378de06a..05ec4672ec 100644 --- a/packages/ui/src/theme/themes/nord.json +++ b/packages/ui/src/theme/themes/nord.json @@ -3,129 +3,40 @@ "name": "Nord", "id": "nord", "light": { - "seeds": { + "palette": { "neutral": "#eceff4", + "ink": "#2e3440", "primary": "#5e81ac", + "accent": "#bf616a", "success": "#8fbcbb", "warning": "#d08770", "error": "#bf616a", "info": "#81a1c1", - "interactive": "#5e81ac", "diffAdd": "#a3be8c", "diffDelete": "#bf616a" }, "overrides": { - "background-base": "#eceff4", - "background-weak": "#e4e8f0", - "background-strong": "#f1f3f8", - "background-stronger": "#f6f8fc", - "border-weak-base": "#d5dbe7", - "border-weak-hover": "#c9d0de", - "border-weak-active": "#bec5d4", - "border-weak-selected": "#b2bacc", - "border-weak-disabled": "#f0f3fa", - "border-weak-focus": "#b9bfd0", - "border-base": "#afb7cb", - "border-hover": "#a3abc1", - "border-active": "#979fb7", - "border-selected": "#8b94ad", - "border-disabled": "#e5e9f2", - "border-focus": "#9ca4ba", - "border-strong-base": "#757f97", - "border-strong-hover": "#69718a", - "border-strong-active": "#5d647d", - "border-strong-selected": "#525970", - "border-strong-disabled": "#c9cedc", - "border-strong-focus": "#636c84", - "surface-diff-add-base": "#e4f0e4", - "surface-diff-delete-base": "#f4e1e4", - "surface-diff-hidden-base": "#dfe6f2", - "text-base": "#2e3440", - "text-weak": "#4c566a", - "text-strong": "#1f2530", + "syntax-keyword": "#5e81ac", "syntax-string": "#a3be8c", - "syntax-primitive": "#bf616a", - "syntax-property": "#5e81ac", - "syntax-type": "#d08770", - "syntax-constant": "#81a1c1", - "syntax-info": "#81a1c1", - "markdown-heading": "#5e81ac", - "markdown-text": "#2e3440", - "markdown-link": "#5e81ac", - "markdown-link-text": "#81a1c1", - "markdown-code": "#a3be8c", - "markdown-block-quote": "#d08770", - "markdown-emph": "#d08770", - "markdown-strong": "#bf616a", - "markdown-horizontal-rule": "#cbd3e1", - "markdown-list-item": "#5e81ac", - "markdown-list-enumeration": "#81a1c1", - "markdown-image": "#5e81ac", - "markdown-image-text": "#81a1c1", - "markdown-code-block": "#5e81ac" + "syntax-primitive": "#b48ead" } }, "dark": { - "seeds": { + "palette": { "neutral": "#2e3440", + "ink": "#e5e9f0", "primary": "#88c0d0", + "accent": "#d57780", "success": "#a3be8c", "warning": "#d08770", "error": "#bf616a", "info": "#81a1c1", - "interactive": "#88c0d0", "diffAdd": "#81a1c1", "diffDelete": "#bf616a" }, "overrides": { - "background-base": "#1f2430", - "background-weak": "#222938", - "background-strong": "#1c202a", - "background-stronger": "#181c24", - "border-weak-base": "#343a47", - "border-weak-hover": "#383f50", - "border-weak-active": "#3d4458", - "border-weak-selected": "#434a62", - "border-weak-disabled": "#151923", - "border-weak-focus": "#3f4359", - "border-base": "#4a5163", - "border-hover": "#515870", - "border-active": "#585f7c", - "border-selected": "#606889", - "border-disabled": "#1b202a", - "border-focus": "#545b78", - "border-strong-base": "#6a7492", - "border-strong-hover": "#747e9f", - "border-strong-active": "#7e88ac", - "border-strong-selected": "#8993b9", - "border-strong-disabled": "#232836", - "border-strong-focus": "#76819f", - "surface-diff-add-base": "#1f2e33", - "surface-diff-delete-base": "#2e212a", - "surface-diff-hidden-base": "#222b3a", - "text-base": "#e5e9f0", - "text-weak": "#a4adbf", - "text-strong": "#f8fafc", - "syntax-string": "#a3be8c", - "syntax-primitive": "#d57780", - "syntax-property": "#88c0d0", - "syntax-type": "#eac196", - "syntax-constant": "#81a1c1", - "syntax-info": "#81a1c1", - "markdown-heading": "#88c0d0", - "markdown-text": "#e5e9f0", - "markdown-link": "#88c0d0", - "markdown-link-text": "#81a1c1", - "markdown-code": "#a3be8c", - "markdown-block-quote": "#d08770", - "markdown-emph": "#d08770", - "markdown-strong": "#bf616a", - "markdown-horizontal-rule": "#2f384a", - "markdown-list-item": "#88c0d0", - "markdown-list-enumeration": "#81a1c1", - "markdown-image": "#88c0d0", - "markdown-image-text": "#81a1c1", - "markdown-code-block": "#cbd3e1" + "syntax-keyword": "#81a1c1", + "syntax-primitive": "#b48ead" } } } diff --git a/packages/ui/src/theme/themes/oc-1.json b/packages/ui/src/theme/themes/oc-1.json index 132825e3fe..7dec9cb802 100644 --- a/packages/ui/src/theme/themes/oc-1.json +++ b/packages/ui/src/theme/themes/oc-1.json @@ -3,9 +3,11 @@ "name": "OC-1", "id": "oc-1", "light": { - "seeds": { + "palette": { "neutral": "#8e8b8b", + "ink": "#656363", "primary": "#dcde8d", + "accent": "#fb4804", "success": "#12c905", "warning": "#ffdc17", "error": "#fc533a", @@ -13,260 +15,14 @@ "interactive": "#034cff", "diffAdd": "#9ff29a", "diffDelete": "#fc533a" - }, - "overrides": { - "background-base": "#f8f7f7", - "background-weak": "var(--smoke-light-3)", - "background-strong": "var(--smoke-light-1)", - "background-stronger": "#fcfcfc", - "surface-base": "var(--smoke-light-alpha-2)", - "base": "var(--smoke-light-alpha-2)", - "surface-base-hover": "#0500000f", - "surface-base-active": "var(--smoke-light-alpha-3)", - "surface-base-interactive-active": "var(--cobalt-light-alpha-3)", - "base2": "var(--smoke-light-alpha-2)", - "base3": "var(--smoke-light-alpha-2)", - "surface-inset-base": "var(--smoke-light-alpha-2)", - "surface-inset-base-hover": "var(--smoke-light-alpha-3)", - "surface-inset-strong": "#1f000017", - "surface-inset-strong-hover": "#1f000017", - "surface-raised-base": "var(--smoke-light-alpha-2)", - "surface-float-base": "var(--smoke-dark-1)", - "surface-float-base-hover": "var(--smoke-dark-2)", - "surface-raised-base-hover": "var(--smoke-light-alpha-3)", - "surface-raised-base-active": "var(--smoke-light-alpha-4)", - "surface-raised-strong": "var(--smoke-light-1)", - "surface-raised-strong-hover": "var(--white)", - "surface-raised-stronger": "var(--white)", - "surface-raised-stronger-hover": "var(--white)", - "surface-weak": "var(--smoke-light-alpha-3)", - "surface-weaker": "var(--smoke-light-alpha-4)", - "surface-strong": "#ffffff", - "surface-raised-stronger-non-alpha": "var(--white)", - "surface-brand-base": "var(--yuzu-light-9)", - "surface-brand-hover": "var(--yuzu-light-10)", - "surface-interactive-base": "var(--cobalt-light-3)", - "surface-interactive-hover": "#E5F0FF", - "surface-interactive-weak": "var(--cobalt-light-2)", - "surface-interactive-weak-hover": "var(--cobalt-light-3)", - "surface-success-base": "var(--apple-light-3)", - "surface-success-weak": "var(--apple-light-2)", - "surface-success-strong": "var(--apple-light-9)", - "surface-warning-base": "var(--solaris-light-3)", - "surface-warning-weak": "var(--solaris-light-2)", - "surface-warning-strong": "var(--solaris-light-9)", - "surface-critical-base": "var(--ember-light-3)", - "surface-critical-weak": "var(--ember-light-2)", - "surface-critical-strong": "var(--ember-light-9)", - "surface-info-base": "var(--lilac-light-3)", - "surface-info-weak": "var(--lilac-light-2)", - "surface-info-strong": "var(--lilac-light-9)", - "surface-diff-unchanged-base": "#ffffff00", - "surface-diff-skip-base": "var(--smoke-light-2)", - "surface-diff-hidden-base": "var(--blue-light-3)", - "surface-diff-hidden-weak": "var(--blue-light-2)", - "surface-diff-hidden-weaker": "var(--blue-light-1)", - "surface-diff-hidden-strong": "var(--blue-light-5)", - "surface-diff-hidden-stronger": "var(--blue-light-9)", - "surface-diff-add-base": "#dafbe0", - "surface-diff-add-weak": "var(--mint-light-2)", - "surface-diff-add-weaker": "var(--mint-light-1)", - "surface-diff-add-strong": "var(--mint-light-5)", - "surface-diff-add-stronger": "var(--mint-light-9)", - "surface-diff-delete-base": "var(--ember-light-3)", - "surface-diff-delete-weak": "var(--ember-light-2)", - "surface-diff-delete-weaker": "var(--ember-light-1)", - "surface-diff-delete-strong": "var(--ember-light-6)", - "surface-diff-delete-stronger": "var(--ember-light-9)", - "input-base": "var(--smoke-light-1)", - "input-hover": "var(--smoke-light-2)", - "input-active": "var(--cobalt-light-1)", - "input-selected": "var(--cobalt-light-4)", - "input-focus": "var(--cobalt-light-1)", - "input-disabled": "var(--smoke-light-4)", - "text-base": "var(--smoke-light-11)", - "text-weak": "var(--smoke-light-9)", - "text-weaker": "var(--smoke-light-8)", - "text-strong": "var(--smoke-light-12)", - "text-invert-base": "var(--smoke-dark-alpha-11)", - "text-invert-weak": "var(--smoke-dark-alpha-9)", - "text-invert-weaker": "var(--smoke-dark-alpha-8)", - "text-invert-strong": "var(--smoke-dark-alpha-12)", - "text-interactive-base": "var(--cobalt-light-9)", - "text-on-brand-base": "var(--smoke-light-alpha-11)", - "text-on-interactive-base": "var(--smoke-light-1)", - "text-on-interactive-weak": "var(--smoke-dark-alpha-11)", - "text-on-success-base": "var(--apple-light-10)", - "text-on-critical-base": "var(--ember-light-10)", - "text-on-critical-weak": "var(--ember-light-8)", - "text-on-critical-strong": "var(--ember-light-12)", - "text-on-warning-base": "var(--smoke-dark-alpha-11)", - "text-on-info-base": "var(--smoke-dark-alpha-11)", - "text-diff-add-base": "var(--mint-light-11)", - "text-diff-delete-base": "var(--ember-light-10)", - "text-diff-delete-strong": "var(--ember-light-12)", - "text-diff-add-strong": "var(--mint-light-12)", - "text-on-info-weak": "var(--smoke-dark-alpha-9)", - "text-on-info-strong": "var(--smoke-dark-alpha-12)", - "text-on-warning-weak": "var(--smoke-dark-alpha-9)", - "text-on-warning-strong": "var(--smoke-dark-alpha-12)", - "text-on-success-weak": "var(--apple-light-6)", - "text-on-success-strong": "var(--apple-light-12)", - "text-on-brand-weak": "var(--smoke-light-alpha-9)", - "text-on-brand-weaker": "var(--smoke-light-alpha-8)", - "text-on-brand-strong": "var(--smoke-light-alpha-12)", - "button-primary-base": "var(--smoke-light-12)", - "button-secondary-base": "#fdfcfc", - "button-secondary-hover": "#faf9f9", - "border-base": "var(--smoke-light-alpha-7)", - "border-hover": "var(--smoke-light-alpha-8)", - "border-active": "var(--smoke-light-alpha-9)", - "border-selected": "var(--cobalt-light-alpha-9)", - "border-disabled": "var(--smoke-light-alpha-8)", - "border-focus": "var(--smoke-light-alpha-9)", - "border-weak-base": "var(--smoke-light-alpha-5)", - "border-strong-base": "var(--smoke-light-alpha-7)", - "border-strong-hover": "var(--smoke-light-alpha-8)", - "border-strong-active": "var(--smoke-light-alpha-7)", - "border-strong-selected": "var(--cobalt-light-alpha-6)", - "border-strong-disabled": "var(--smoke-light-alpha-6)", - "border-strong-focus": "var(--smoke-light-alpha-7)", - "border-weak-hover": "var(--smoke-light-alpha-6)", - "border-weak-active": "var(--smoke-light-alpha-7)", - "border-weak-selected": "var(--cobalt-light-alpha-5)", - "border-weak-disabled": "var(--smoke-light-alpha-6)", - "border-weak-focus": "var(--smoke-light-alpha-7)", - "border-interactive-base": "var(--cobalt-light-7)", - "border-interactive-hover": "var(--cobalt-light-8)", - "border-interactive-active": "var(--cobalt-light-9)", - "border-interactive-selected": "var(--cobalt-light-9)", - "border-interactive-disabled": "var(--smoke-light-8)", - "border-interactive-focus": "var(--cobalt-light-9)", - "border-success-base": "var(--apple-light-6)", - "border-success-hover": "var(--apple-light-7)", - "border-success-selected": "var(--apple-light-9)", - "border-warning-base": "var(--solaris-light-6)", - "border-warning-hover": "var(--solaris-light-7)", - "border-warning-selected": "var(--solaris-light-9)", - "border-critical-base": "var(--ember-light-6)", - "border-critical-hover": "var(--ember-light-7)", - "border-critical-selected": "var(--ember-light-9)", - "border-info-base": "var(--lilac-light-6)", - "border-info-hover": "var(--lilac-light-7)", - "border-info-selected": "var(--lilac-light-9)", - "icon-base": "var(--smoke-light-9)", - "icon-hover": "var(--smoke-light-11)", - "icon-active": "var(--smoke-light-12)", - "icon-selected": "var(--smoke-light-12)", - "icon-disabled": "var(--smoke-light-8)", - "icon-focus": "var(--smoke-light-12)", - "icon-invert-base": "#ffffff", - "icon-weak-base": "var(--smoke-light-7)", - "icon-weak-hover": "var(--smoke-light-8)", - "icon-weak-active": "var(--smoke-light-9)", - "icon-weak-selected": "var(--smoke-light-10)", - "icon-weak-disabled": "var(--smoke-light-6)", - "icon-weak-focus": "var(--smoke-light-9)", - "icon-strong-base": "var(--smoke-light-12)", - "icon-strong-hover": "#151313", - "icon-strong-active": "#020202", - "icon-strong-selected": "#020202", - "icon-strong-disabled": "var(--smoke-light-8)", - "icon-strong-focus": "#020202", - "icon-brand-base": "var(--smoke-light-12)", - "icon-interactive-base": "var(--cobalt-light-9)", - "icon-success-base": "var(--apple-light-7)", - "icon-success-hover": "var(--apple-light-8)", - "icon-success-active": "var(--apple-light-11)", - "icon-warning-base": "var(--amber-light-7)", - "icon-warning-hover": "var(--amber-light-8)", - "icon-warning-active": "var(--amber-light-11)", - "icon-critical-base": "var(--ember-light-10)", - "icon-critical-hover": "var(--ember-light-11)", - "icon-critical-active": "var(--ember-light-12)", - "icon-info-base": "var(--lilac-light-7)", - "icon-info-hover": "var(--lilac-light-8)", - "icon-info-active": "var(--lilac-light-11)", - "icon-on-brand-base": "var(--smoke-light-alpha-11)", - "icon-on-brand-hover": "var(--smoke-light-alpha-12)", - "icon-on-brand-selected": "var(--smoke-light-alpha-12)", - "icon-on-interactive-base": "var(--smoke-light-1)", - "icon-agent-plan-base": "var(--purple-light-9)", - "icon-agent-docs-base": "var(--amber-light-9)", - "icon-agent-ask-base": "var(--cyan-light-9)", - "icon-agent-build-base": "var(--cobalt-light-9)", - "icon-on-success-base": "var(--apple-light-alpha-9)", - "icon-on-success-hover": "var(--apple-light-alpha-10)", - "icon-on-success-selected": "var(--apple-light-alpha-11)", - "icon-on-warning-base": "var(--amber-lightalpha-9)", - "icon-on-warning-hover": "var(--amber-lightalpha-10)", - "icon-on-warning-selected": "var(--amber-lightalpha-11)", - "icon-on-critical-base": "var(--ember-light-alpha-9)", - "icon-on-critical-hover": "var(--ember-light-alpha-10)", - "icon-on-critical-selected": "var(--ember-light-alpha-11)", - "icon-on-info-base": "var(--lilac-light-9)", - "icon-on-info-hover": "var(--lilac-light-alpha-10)", - "icon-on-info-selected": "var(--lilac-light-alpha-11)", - "icon-diff-add-base": "var(--mint-light-11)", - "icon-diff-add-hover": "var(--mint-light-12)", - "icon-diff-add-active": "var(--mint-light-12)", - "icon-diff-delete-base": "var(--ember-light-10)", - "icon-diff-delete-hover": "var(--ember-light-11)", - "syntax-comment": "var(--text-weak)", - "syntax-regexp": "var(--text-base)", - "syntax-string": "#006656", - "syntax-keyword": "var(--text-weak)", - "syntax-primitive": "#fb4804", - "syntax-operator": "var(--text-base)", - "syntax-variable": "var(--text-strong)", - "syntax-property": "#ed6dc8", - "syntax-type": "#596600", - "syntax-constant": "#007b80", - "syntax-punctuation": "var(--text-base)", - "syntax-object": "var(--text-strong)", - "syntax-success": "var(--apple-light-10)", - "syntax-warning": "var(--amber-light-10)", - "syntax-critical": "var(--ember-light-10)", - "syntax-info": "#0092a8", - "syntax-diff-add": "var(--mint-light-11)", - "syntax-diff-delete": "var(--ember-light-11)", - "syntax-diff-unknown": "#ff0000", - "markdown-heading": "#d68c27", - "markdown-text": "#1a1a1a", - "markdown-link": "#3b7dd8", - "markdown-link-text": "#318795", - "markdown-code": "#3d9a57", - "markdown-block-quote": "#b0851f", - "markdown-emph": "#b0851f", - "markdown-strong": "#d68c27", - "markdown-horizontal-rule": "#8a8a8a", - "markdown-list-item": "#3b7dd8", - "markdown-list-enumeration": "#318795", - "markdown-image": "#3b7dd8", - "markdown-image-text": "#318795", - "markdown-code-block": "#1a1a1a", - "border-color": "#ffffff", - "border-weaker-base": "var(--smoke-light-alpha-3)", - "button-ghost-hover": "var(--smoke-light-alpha-2)", - "button-ghost-hover2": "var(--smoke-light-alpha-3)", - "avatar-background-pink": "#feeef8", - "avatar-background-mint": "#e1fbf4", - "avatar-background-orange": "#fff1e7", - "avatar-background-purple": "#f9f1fe", - "avatar-background-cyan": "#e7f9fb", - "avatar-background-lime": "#eefadc", - "avatar-text-pink": "#cd1d8d", - "avatar-text-mint": "#147d6f", - "avatar-text-orange": "#ed5f00", - "avatar-text-purple": "#8445bc", - "avatar-text-cyan": "#0894b3", - "avatar-text-lime": "#5d770d" } }, "dark": { - "seeds": { + "palette": { "neutral": "#716c6b", + "ink": "#b7b1b1", "primary": "#fab283", + "accent": "#ffba92", "success": "#12c905", "warning": "#fcd53a", "error": "#fc533a", @@ -274,254 +30,6 @@ "interactive": "#034cff", "diffAdd": "#c8ffc4", "diffDelete": "#fc533a" - }, - "overrides": { - "background-base": "var(--smoke-dark-1)", - "background-weak": "#1c1717", - "background-strong": "#151313", - "background-stronger": "#191515", - "surface-base": "var(--smoke-dark-alpha-2)", - "base": "var(--smoke-dark-alpha-2)", - "surface-base-hover": "#e0b7b716", - "surface-base-active": "var(--smoke-dark-alpha-3)", - "surface-base-interactive-active": "var(--cobalt-dark-alpha-2)", - "base2": "var(--smoke-dark-alpha-2)", - "base3": "var(--smoke-dark-alpha-2)", - "surface-inset-base": "#0e0b0b7f", - "surface-inset-base-hover": "#0e0b0b7f", - "surface-inset-strong": "#060505cc", - "surface-inset-strong-hover": "#060505cc", - "surface-raised-base": "var(--smoke-dark-alpha-3)", - "surface-float-base": "var(--smoke-dark-1)", - "surface-float-base-hover": "var(--smoke-dark-2)", - "surface-raised-base-hover": "var(--smoke-dark-alpha-4)", - "surface-raised-base-active": "var(--smoke-dark-alpha-5)", - "surface-raised-strong": "var(--smoke-dark-alpha-4)", - "surface-raised-strong-hover": "var(--smoke-dark-alpha-6)", - "surface-raised-stronger": "var(--smoke-dark-alpha-6)", - "surface-raised-stronger-hover": "var(--smoke-dark-alpha-7)", - "surface-weak": "var(--smoke-dark-alpha-4)", - "surface-weaker": "var(--smoke-dark-alpha-5)", - "surface-strong": "var(--smoke-dark-alpha-7)", - "surface-raised-stronger-non-alpha": "var(--smoke-dark-3)", - "surface-brand-base": "var(--yuzu-light-9)", - "surface-brand-hover": "var(--yuzu-light-10)", - "surface-interactive-base": "var(--cobalt-dark-3)", - "surface-interactive-hover": "#0A1D4D", - "surface-interactive-weak": "var(--cobalt-dark-2)", - "surface-interactive-weak-hover": "var(--cobalt-light-3)", - "surface-success-base": "var(--apple-dark-3)", - "surface-success-weak": "var(--apple-dark-2)", - "surface-success-strong": "var(--apple-dark-9)", - "surface-warning-base": "var(--solaris-light-3)", - "surface-warning-weak": "var(--solaris-light-2)", - "surface-warning-strong": "var(--solaris-light-9)", - "surface-critical-base": "var(--ember-dark-3)", - "surface-critical-weak": "var(--ember-dark-2)", - "surface-critical-strong": "var(--ember-dark-9)", - "surface-info-base": "var(--lilac-light-3)", - "surface-info-weak": "var(--lilac-light-2)", - "surface-info-strong": "var(--lilac-light-9)", - "surface-diff-unchanged-base": "var(--smoke-dark-1)", - "surface-diff-skip-base": "var(--smoke-dark-alpha-1)", - "surface-diff-hidden-base": "var(--blue-dark-2)", - "surface-diff-hidden-weak": "var(--blue-dark-1)", - "surface-diff-hidden-weaker": "var(--blue-dark-3)", - "surface-diff-hidden-strong": "var(--blue-dark-5)", - "surface-diff-hidden-stronger": "var(--blue-dark-11)", - "surface-diff-add-base": "var(--mint-dark-3)", - "surface-diff-add-weak": "var(--mint-dark-4)", - "surface-diff-add-weaker": "var(--mint-dark-3)", - "surface-diff-add-strong": "var(--mint-dark-5)", - "surface-diff-add-stronger": "var(--mint-dark-11)", - "surface-diff-delete-base": "var(--ember-dark-3)", - "surface-diff-delete-weak": "var(--ember-dark-4)", - "surface-diff-delete-weaker": "var(--ember-dark-3)", - "surface-diff-delete-strong": "var(--ember-dark-5)", - "surface-diff-delete-stronger": "var(--ember-dark-11)", - "input-base": "var(--smoke-dark-2)", - "input-hover": "var(--smoke-dark-2)", - "input-active": "var(--cobalt-dark-1)", - "input-selected": "var(--cobalt-dark-2)", - "input-focus": "var(--cobalt-dark-1)", - "input-disabled": "var(--smoke-dark-4)", - "text-base": "var(--smoke-dark-alpha-11)", - "text-weak": "var(--smoke-dark-alpha-9)", - "text-weaker": "var(--smoke-dark-alpha-8)", - "text-strong": "var(--smoke-dark-alpha-12)", - "text-invert-base": "var(--smoke-dark-alpha-11)", - "text-invert-weak": "var(--smoke-dark-alpha-9)", - "text-invert-weaker": "var(--smoke-dark-alpha-8)", - "text-invert-strong": "var(--smoke-dark-alpha-12)", - "text-interactive-base": "var(--cobalt-dark-11)", - "text-on-brand-base": "var(--smoke-dark-alpha-11)", - "text-on-interactive-base": "var(--smoke-dark-12)", - "text-on-interactive-weak": "var(--smoke-dark-alpha-11)", - "text-on-success-base": "var(--apple-dark-9)", - "text-on-critical-base": "var(--ember-dark-9)", - "text-on-critical-weak": "var(--ember-dark-8)", - "text-on-critical-strong": "var(--ember-dark-12)", - "text-on-warning-base": "var(--smoke-dark-alpha-11)", - "text-on-info-base": "var(--smoke-dark-alpha-11)", - "text-diff-add-base": "var(--mint-dark-11)", - "text-diff-delete-base": "var(--ember-dark-9)", - "text-diff-delete-strong": "var(--ember-dark-12)", - "text-diff-add-strong": "var(--mint-dark-8)", - "text-on-info-weak": "var(--smoke-dark-alpha-9)", - "text-on-info-strong": "var(--smoke-dark-alpha-12)", - "text-on-warning-weak": "var(--smoke-dark-alpha-9)", - "text-on-warning-strong": "var(--smoke-dark-alpha-12)", - "text-on-success-weak": "var(--apple-dark-8)", - "text-on-success-strong": "var(--apple-dark-12)", - "text-on-brand-weak": "var(--smoke-dark-alpha-9)", - "text-on-brand-weaker": "var(--smoke-dark-alpha-8)", - "text-on-brand-strong": "var(--smoke-dark-alpha-12)", - "button-primary-base": "var(--smoke-dark-12)", - "button-secondary-base": "#231f1f", - "button-secondary-hover": "#2a2727", - "border-base": "var(--smoke-dark-alpha-7)", - "border-hover": "var(--smoke-dark-alpha-8)", - "border-active": "var(--smoke-dark-alpha-9)", - "border-selected": "var(--cobalt-dark-alpha-11)", - "border-disabled": "var(--smoke-dark-alpha-8)", - "border-focus": "var(--smoke-dark-alpha-9)", - "border-weak-base": "var(--smoke-dark-alpha-6)", - "border-strong-base": "var(--smoke-dark-alpha-8)", - "border-strong-hover": "var(--smoke-dark-alpha-7)", - "border-strong-active": "var(--smoke-dark-alpha-8)", - "border-strong-selected": "var(--cobalt-dark-alpha-6)", - "border-strong-disabled": "var(--smoke-dark-alpha-6)", - "border-strong-focus": "var(--smoke-dark-alpha-8)", - "border-weak-hover": "var(--smoke-dark-alpha-7)", - "border-weak-active": "var(--smoke-dark-alpha-8)", - "border-weak-selected": "var(--cobalt-dark-alpha-6)", - "border-weak-disabled": "var(--smoke-dark-alpha-6)", - "border-weak-focus": "var(--smoke-dark-alpha-8)", - "border-interactive-base": "var(--cobalt-light-7)", - "border-interactive-hover": "var(--cobalt-light-8)", - "border-interactive-active": "var(--cobalt-light-9)", - "border-interactive-selected": "var(--cobalt-light-9)", - "border-interactive-disabled": "var(--smoke-light-8)", - "border-interactive-focus": "var(--cobalt-light-9)", - "border-success-base": "var(--apple-light-6)", - "border-success-hover": "var(--apple-light-7)", - "border-success-selected": "var(--apple-light-9)", - "border-warning-base": "var(--solaris-light-6)", - "border-warning-hover": "var(--solaris-light-7)", - "border-warning-selected": "var(--solaris-light-9)", - "border-critical-base": "var(--ember-dark-5)", - "border-critical-hover": "var(--ember-dark-7)", - "border-critical-selected": "var(--ember-dark-9)", - "border-info-base": "var(--lilac-light-6)", - "border-info-hover": "var(--lilac-light-7)", - "border-info-selected": "var(--lilac-light-9)", - "icon-base": "var(--smoke-dark-9)", - "icon-hover": "var(--smoke-dark-10)", - "icon-active": "var(--smoke-dark-11)", - "icon-selected": "var(--smoke-dark-12)", - "icon-disabled": "var(--smoke-dark-7)", - "icon-focus": "var(--smoke-dark-12)", - "icon-invert-base": "var(--smoke-dark-1)", - "icon-weak-base": "var(--smoke-dark-6)", - "icon-weak-hover": "var(--smoke-light-7)", - "icon-weak-active": "var(--smoke-light-8)", - "icon-weak-selected": "var(--smoke-light-9)", - "icon-weak-disabled": "var(--smoke-light-4)", - "icon-weak-focus": "var(--smoke-light-9)", - "icon-strong-base": "var(--smoke-dark-12)", - "icon-strong-hover": "#f6f3f3", - "icon-strong-active": "#fcfcfc", - "icon-strong-selected": "#fdfcfc", - "icon-strong-disabled": "var(--smoke-dark-8)", - "icon-strong-focus": "#fdfcfc", - "icon-brand-base": "var(--white)", - "icon-interactive-base": "var(--cobalt-dark-11)", - "icon-success-base": "var(--apple-dark-9)", - "icon-success-hover": "var(--apple-dark-10)", - "icon-success-active": "var(--apple-dark-11)", - "icon-warning-base": "var(--amber-dark-9)", - "icon-warning-hover": "var(--amber-dark-8)", - "icon-warning-active": "var(--amber-dark-11)", - "icon-critical-base": "var(--ember-dark-9)", - "icon-critical-hover": "var(--ember-dark-11)", - "icon-critical-active": "var(--ember-dark-12)", - "icon-info-base": "var(--lilac-dark-7)", - "icon-info-hover": "var(--lilac-dark-8)", - "icon-info-active": "var(--lilac-dark-11)", - "icon-on-brand-base": "var(--smoke-light-alpha-11)", - "icon-on-brand-hover": "var(--smoke-light-alpha-12)", - "icon-on-brand-selected": "var(--smoke-light-alpha-12)", - "icon-on-interactive-base": "var(--smoke-dark-12)", - "icon-agent-plan-base": "var(--purple-dark-9)", - "icon-agent-docs-base": "var(--amber-dark-9)", - "icon-agent-ask-base": "var(--cyan-dark-9)", - "icon-agent-build-base": "var(--cobalt-dark-11)", - "icon-on-success-base": "var(--apple-dark-alpha-9)", - "icon-on-success-hover": "var(--apple-dark-alpha-10)", - "icon-on-success-selected": "var(--apple-dark-alpha-11)", - "icon-on-warning-base": "var(--amber-darkalpha-9)", - "icon-on-warning-hover": "var(--amber-darkalpha-10)", - "icon-on-warning-selected": "var(--amber-darkalpha-11)", - "icon-on-critical-base": "var(--ember-dark-alpha-9)", - "icon-on-critical-hover": "var(--ember-dark-alpha-10)", - "icon-on-critical-selected": "var(--ember-dark-alpha-11)", - "icon-on-info-base": "var(--lilac-dark-9)", - "icon-on-info-hover": "var(--lilac-dark-alpha-10)", - "icon-on-info-selected": "var(--lilac-dark-alpha-11)", - "icon-diff-add-base": "var(--mint-dark-11)", - "icon-diff-add-hover": "var(--mint-dark-10)", - "icon-diff-add-active": "var(--mint-dark-11)", - "icon-diff-delete-base": "var(--ember-dark-9)", - "icon-diff-delete-hover": "var(--ember-dark-10)", - "syntax-comment": "var(--text-weak)", - "syntax-regexp": "var(--text-base)", - "syntax-string": "#00ceb9", - "syntax-keyword": "var(--text-weak)", - "syntax-primitive": "#ffba92", - "syntax-operator": "var(--text-weak)", - "syntax-variable": "var(--text-strong)", - "syntax-property": "#ff9ae2", - "syntax-type": "#ecf58c", - "syntax-constant": "#93e9f6", - "syntax-punctuation": "var(--text-weak)", - "syntax-object": "var(--text-strong)", - "syntax-success": "var(--apple-dark-10)", - "syntax-warning": "var(--amber-dark-10)", - "syntax-critical": "var(--ember-dark-10)", - "syntax-info": "#93e9f6", - "syntax-diff-add": "var(--mint-dark-11)", - "syntax-diff-delete": "var(--ember-dark-11)", - "syntax-diff-unknown": "#ff0000", - "markdown-heading": "#9d7cd8", - "markdown-text": "#eeeeee", - "markdown-link": "#fab283", - "markdown-link-text": "#56b6c2", - "markdown-code": "#7fd88f", - "markdown-block-quote": "#e5c07b", - "markdown-emph": "#e5c07b", - "markdown-strong": "#f5a742", - "markdown-horizontal-rule": "#808080", - "markdown-list-item": "#fab283", - "markdown-list-enumeration": "#56b6c2", - "markdown-image": "#fab283", - "markdown-image-text": "#56b6c2", - "markdown-code-block": "#eeeeee", - "border-color": "#ffffff", - "border-weaker-base": "var(--smoke-dark-alpha-3)", - "button-ghost-hover": "var(--smoke-dark-alpha-2)", - "button-ghost-hover2": "var(--smoke-dark-alpha-3)", - "avatar-background-pink": "#501b3f", - "avatar-background-mint": "#033a34", - "avatar-background-orange": "#5f2a06", - "avatar-background-purple": "#432155", - "avatar-background-cyan": "#0f3058", - "avatar-background-lime": "#2b3711", - "avatar-text-pink": "#e34ba9", - "avatar-text-mint": "#95f3d9", - "avatar-text-orange": "#ff802b", - "avatar-text-purple": "#9d5bd2", - "avatar-text-cyan": "#369eff", - "avatar-text-lime": "#c4f042" } } } diff --git a/packages/ui/src/theme/themes/oc-2.json b/packages/ui/src/theme/themes/oc-2.json index 73ca57da9d..fdf0c2caf4 100644 --- a/packages/ui/src/theme/themes/oc-2.json +++ b/packages/ui/src/theme/themes/oc-2.json @@ -3,7 +3,7 @@ "name": "OC-2", "id": "oc-2", "light": { - "seeds": { + "palette": { "neutral": "#8f8f8f", "primary": "#dcde8d", "success": "#12c905", @@ -13,258 +13,10 @@ "interactive": "#034cff", "diffAdd": "#9ff29a", "diffDelete": "#fc533a" - }, - "overrides": { - "background-base": "#f8f8f8", - "background-weak": "#f3f3f3", - "background-strong": "#fcfcfc", - "background-stronger": "#fcfcfc", - "surface-base": "#00000008", - "base": "#00000008", - "surface-base-hover": "#0000000f", - "surface-base-active": "#0000000d", - "surface-base-interactive-active": "var(--cobalt-light-alpha-3)", - "base2": "#00000008", - "base3": "#00000008", - "surface-inset-base": "#00000008", - "surface-inset-base-hover": "#0000000d", - "surface-inset-strong": "#00000017", - "surface-inset-strong-hover": "#00000017", - "surface-raised-base": "#00000008", - "surface-float-base": "#161616", - "surface-float-base-hover": "#1c1c1c", - "surface-raised-base-hover": "#0000000d", - "surface-raised-base-active": "#00000017", - "surface-raised-strong": "#fcfcfc", - "surface-raised-strong-hover": "var(--white)", - "surface-raised-stronger": "var(--white)", - "surface-raised-stronger-hover": "var(--white)", - "surface-weak": "#0000000d", - "surface-weaker": "#00000012", - "surface-strong": "#ffffff", - "surface-raised-stronger-non-alpha": "var(--white)", - "surface-brand-base": "var(--yuzu-light-9)", - "surface-brand-hover": "var(--yuzu-light-10)", - "surface-interactive-base": "var(--cobalt-light-3)", - "surface-interactive-hover": "#E5F0FF", - "surface-interactive-weak": "var(--cobalt-light-2)", - "surface-interactive-weak-hover": "var(--cobalt-light-3)", - "surface-success-base": "var(--apple-light-3)", - "surface-success-weak": "var(--apple-light-2)", - "surface-success-strong": "var(--apple-light-9)", - "surface-warning-base": "var(--solaris-light-3)", - "surface-warning-weak": "var(--solaris-light-2)", - "surface-warning-strong": "var(--solaris-light-9)", - "surface-critical-base": "var(--ember-light-3)", - "surface-critical-weak": "var(--ember-light-2)", - "surface-critical-strong": "var(--ember-light-9)", - "surface-info-base": "var(--lilac-light-3)", - "surface-info-weak": "var(--lilac-light-2)", - "surface-info-strong": "var(--lilac-light-9)", - "surface-diff-unchanged-base": "#ffffff00", - "surface-diff-skip-base": "#f8f8f8", - "surface-diff-hidden-base": "var(--blue-light-3)", - "surface-diff-hidden-weak": "var(--blue-light-2)", - "surface-diff-hidden-weaker": "var(--blue-light-1)", - "surface-diff-hidden-strong": "var(--blue-light-5)", - "surface-diff-hidden-stronger": "var(--blue-light-9)", - "surface-diff-add-base": "#dafbe0", - "surface-diff-add-weak": "var(--mint-light-2)", - "surface-diff-add-weaker": "var(--mint-light-1)", - "surface-diff-add-strong": "var(--mint-light-5)", - "surface-diff-add-stronger": "var(--mint-light-9)", - "surface-diff-delete-base": "var(--ember-light-3)", - "surface-diff-delete-weak": "var(--ember-light-2)", - "surface-diff-delete-weaker": "var(--ember-light-1)", - "surface-diff-delete-strong": "var(--ember-light-6)", - "surface-diff-delete-stronger": "var(--ember-light-9)", - "input-base": "#fcfcfc", - "input-hover": "#f8f8f8", - "input-active": "var(--cobalt-light-1)", - "input-selected": "var(--cobalt-light-4)", - "input-focus": "var(--cobalt-light-1)", - "input-disabled": "#ededed", - "text-base": "#6f6f6f", - "text-weak": "#8f8f8f", - "text-weaker": "#c7c7c7", - "text-strong": "#171717", - "text-invert-base": "#ffffff96", - "text-invert-weak": "#ffffff63", - "text-invert-weaker": "#ffffff40", - "text-invert-strong": "#ffffffeb", - "text-interactive-base": "var(--cobalt-light-9)", - "text-on-brand-base": "#0000008f", - "text-on-interactive-base": "#fcfcfc", - "text-on-interactive-weak": "#ffffff96", - "text-on-success-base": "var(--apple-light-10)", - "text-on-critical-base": "var(--ember-light-10)", - "text-on-critical-weak": "var(--ember-light-8)", - "text-on-critical-strong": "var(--ember-light-12)", - "text-on-warning-base": "#ffffff96", - "text-on-info-base": "#ffffff96", - "text-diff-add-base": "var(--mint-light-11)", - "text-diff-delete-base": "var(--ember-light-10)", - "text-diff-delete-strong": "var(--ember-light-12)", - "text-diff-add-strong": "var(--mint-light-12)", - "text-on-info-weak": "#ffffff63", - "text-on-info-strong": "#ffffffeb", - "text-on-warning-weak": "#ffffff63", - "text-on-warning-strong": "#ffffffeb", - "text-on-success-weak": "var(--apple-light-6)", - "text-on-success-strong": "var(--apple-light-12)", - "text-on-brand-weak": "#00000070", - "text-on-brand-weaker": "#00000038", - "text-on-brand-strong": "#000000e8", - "button-primary-base": "#171717", - "button-secondary-base": "#fcfcfc", - "button-secondary-hover": "FFFFFF0A", - "border-base": "#00000024", - "border-hover": "#00000038", - "border-active": "#00000070", - "border-selected": "var(--cobalt-light-alpha-9)", - "border-disabled": "#00000038", - "border-focus": "#00000070", - "border-weak-base": "#e5e5e5", - "border-strong-base": "#00000024", - "border-strong-hover": "#00000038", - "border-strong-active": "#00000024", - "border-strong-selected": "var(--cobalt-light-alpha-6)", - "border-strong-disabled": "#0000001c", - "border-strong-focus": "#00000024", - "border-weak-hover": "#0000001c", - "border-weak-active": "#00000024", - "border-weak-selected": "var(--cobalt-light-alpha-5)", - "border-weak-disabled": "#0000001c", - "border-weak-focus": "#00000024", - "border-interactive-base": "var(--cobalt-light-7)", - "border-interactive-hover": "var(--cobalt-light-8)", - "border-interactive-active": "var(--cobalt-light-9)", - "border-interactive-selected": "var(--cobalt-light-9)", - "border-interactive-disabled": "#c7c7c7", - "border-interactive-focus": "var(--cobalt-light-9)", - "border-success-base": "var(--apple-light-6)", - "border-success-hover": "var(--apple-light-7)", - "border-success-selected": "var(--apple-light-9)", - "border-warning-base": "var(--solaris-light-6)", - "border-warning-hover": "var(--solaris-light-7)", - "border-warning-selected": "var(--solaris-light-9)", - "border-critical-base": "var(--ember-light-6)", - "border-critical-hover": "var(--ember-light-7)", - "border-critical-selected": "var(--ember-light-9)", - "border-info-base": "var(--lilac-light-6)", - "border-info-hover": "var(--lilac-light-7)", - "border-info-selected": "var(--lilac-light-9)", - "icon-base": "#8f8f8f", - "icon-hover": "#6f6f6f", - "icon-active": "#171717", - "icon-selected": "#171717", - "icon-disabled": "#c7c7c7", - "icon-focus": "#171717", - "icon-invert-base": "#ffffff", - "icon-weak-base": "#dbdbdb", - "icon-weak-hover": "#c7c7c7", - "icon-weak-active": "#8f8f8f", - "icon-weak-selected": "#858585", - "icon-weak-disabled": "#e2e2e2", - "icon-weak-focus": "#8f8f8f", - "icon-strong-base": "#171717", - "icon-strong-hover": "#151515", - "icon-strong-active": "#020202", - "icon-strong-selected": "#020202", - "icon-strong-disabled": "#e2e2e2", - "icon-strong-focus": "#020202", - "icon-brand-base": "#171717", - "icon-interactive-base": "var(--cobalt-light-9)", - "icon-success-base": "var(--apple-light-7)", - "icon-success-hover": "var(--apple-light-8)", - "icon-success-active": "var(--apple-light-11)", - "icon-warning-base": "var(--amber-light-7)", - "icon-warning-hover": "var(--amber-light-8)", - "icon-warning-active": "var(--amber-light-11)", - "icon-critical-base": "var(--ember-light-10)", - "icon-critical-hover": "var(--ember-light-11)", - "icon-critical-active": "var(--ember-light-12)", - "icon-info-base": "var(--lilac-light-7)", - "icon-info-hover": "var(--lilac-light-8)", - "icon-info-active": "var(--lilac-light-11)", - "icon-on-brand-base": "#0000008f", - "icon-on-brand-hover": "#000000e8", - "icon-on-brand-selected": "#000000e8", - "icon-on-interactive-base": "#fcfcfc", - "icon-agent-plan-base": "var(--purple-light-9)", - "icon-agent-docs-base": "var(--amber-light-9)", - "icon-agent-ask-base": "var(--cyan-light-9)", - "icon-agent-build-base": "var(--cobalt-light-9)", - "icon-on-success-base": "var(--apple-light-alpha-9)", - "icon-on-success-hover": "var(--apple-light-alpha-10)", - "icon-on-success-selected": "var(--apple-light-alpha-11)", - "icon-on-warning-base": "var(--amber-lightalpha-9)", - "icon-on-warning-hover": "var(--amber-lightalpha-10)", - "icon-on-warning-selected": "var(--amber-lightalpha-11)", - "icon-on-critical-base": "var(--ember-light-alpha-9)", - "icon-on-critical-hover": "var(--ember-light-alpha-10)", - "icon-on-critical-selected": "var(--ember-light-alpha-11)", - "icon-on-info-base": "var(--lilac-light-9)", - "icon-on-info-hover": "var(--lilac-light-alpha-10)", - "icon-on-info-selected": "var(--lilac-light-alpha-11)", - "icon-diff-add-base": "var(--mint-light-11)", - "icon-diff-add-hover": "var(--mint-light-12)", - "icon-diff-add-active": "var(--mint-light-12)", - "icon-diff-delete-base": "var(--ember-light-10)", - "icon-diff-delete-hover": "var(--ember-light-11)", - "syntax-comment": "var(--text-weak)", - "syntax-regexp": "var(--text-base)", - "syntax-string": "#006656", - "syntax-keyword": "var(--text-weak)", - "syntax-primitive": "#fb4804", - "syntax-operator": "var(--text-base)", - "syntax-variable": "var(--text-strong)", - "syntax-property": "#ed6dc8", - "syntax-type": "#596600", - "syntax-constant": "#007b80", - "syntax-punctuation": "var(--text-base)", - "syntax-object": "var(--text-strong)", - "syntax-success": "var(--apple-light-10)", - "syntax-warning": "var(--amber-light-10)", - "syntax-critical": "var(--ember-light-10)", - "syntax-info": "#0092a8", - "syntax-diff-add": "var(--mint-light-11)", - "syntax-diff-delete": "var(--ember-light-11)", - "syntax-diff-unknown": "#ff0000", - "markdown-heading": "#d68c27", - "markdown-text": "#1a1a1a", - "markdown-link": "#3b7dd8", - "markdown-link-text": "#318795", - "markdown-code": "#3d9a57", - "markdown-block-quote": "#b0851f", - "markdown-emph": "#b0851f", - "markdown-strong": "#d68c27", - "markdown-horizontal-rule": "#8a8a8a", - "markdown-list-item": "#3b7dd8", - "markdown-list-enumeration": "#318795", - "markdown-image": "#3b7dd8", - "markdown-image-text": "#318795", - "markdown-code-block": "#1a1a1a", - "border-color": "#ffffff", - "border-weaker-base": "#efefef", - "button-ghost-hover": "#00000008", - "button-ghost-hover2": "#0000000d", - "avatar-background-pink": "#feeef8", - "avatar-background-mint": "#e1fbf4", - "avatar-background-orange": "#fff1e7", - "avatar-background-purple": "#f9f1fe", - "avatar-background-cyan": "#e7f9fb", - "avatar-background-lime": "#eefadc", - "avatar-text-pink": "#cd1d8d", - "avatar-text-mint": "#147d6f", - "avatar-text-orange": "#ed5f00", - "avatar-text-purple": "#8445bc", - "avatar-text-cyan": "#0894b3", - "avatar-text-lime": "#5d770d" } }, "dark": { - "seeds": { + "palette": { "neutral": "#707070", "primary": "#fab283", "success": "#12c905", @@ -274,249 +26,6 @@ "interactive": "#034cff", "diffAdd": "#c8ffc4", "diffDelete": "#fc533a" - }, - "overrides": { - "base": "#ffffff08", - "base2": "#ffffff08", - "base3": "#ffffff08", - "background-base": "#101010", - "background-weak": "#1E1E1E", - "background-strong": "#121212", - "background-stronger": "#151515", - "surface-base": "#ffffff08", - "surface-base-hover": "#FFFFFF0A", - "surface-base-active": "#ffffff0f", - "surface-base-interactive-active": "var(--cobalt-dark-alpha-2)", - "surface-inset-base": "#0000007f", - "surface-inset-base-hover": "#0000007f", - "surface-inset-strong": "#000000cc", - "surface-inset-strong-hover": "#000000cc", - "surface-raised-base": "#ffffff0f", - "surface-float-base": "#161616", - "surface-float-base-hover": "#1c1c1c", - "surface-raised-base-hover": "#ffffff14", - "surface-raised-base-active": "#ffffff1a", - "surface-raised-strong": "#ffffff14", - "surface-raised-strong-hover": "#ffffff21", - "surface-raised-stronger": "#ffffff21", - "surface-raised-stronger-hover": "#ffffff2b", - "surface-weak": "#ffffff14", - "surface-weaker": "#ffffff1a", - "surface-strong": "#ffffff2b", - "surface-raised-stronger-non-alpha": "#1B1B1B", - "surface-brand-base": "var(--yuzu-light-9)", - "surface-brand-hover": "var(--yuzu-light-10)", - "surface-interactive-base": "var(--cobalt-dark-3)", - "surface-interactive-hover": "#0A1D4D", - "surface-interactive-weak": "var(--cobalt-dark-2)", - "surface-interactive-weak-hover": "var(--cobalt-light-3)", - "surface-success-base": "var(--apple-dark-3)", - "surface-success-weak": "var(--apple-dark-2)", - "surface-success-strong": "var(--apple-dark-9)", - "surface-warning-base": "var(--solaris-light-3)", - "surface-warning-weak": "var(--solaris-light-2)", - "surface-warning-strong": "var(--solaris-light-9)", - "surface-critical-base": "var(--ember-dark-3)", - "surface-critical-weak": "var(--ember-dark-2)", - "surface-critical-strong": "var(--ember-dark-9)", - "surface-info-base": "var(--lilac-light-3)", - "surface-info-weak": "var(--lilac-light-2)", - "surface-info-strong": "var(--lilac-light-9)", - "surface-diff-unchanged-base": "#161616", - "surface-diff-skip-base": "#00000000", - "surface-diff-hidden-base": "var(--blue-dark-2)", - "surface-diff-hidden-weak": "var(--blue-dark-1)", - "surface-diff-hidden-weaker": "var(--blue-dark-3)", - "surface-diff-hidden-strong": "var(--blue-dark-5)", - "surface-diff-hidden-stronger": "var(--blue-dark-11)", - "surface-diff-add-base": "var(--mint-dark-3)", - "surface-diff-add-weak": "var(--mint-dark-4)", - "surface-diff-add-weaker": "var(--mint-dark-3)", - "surface-diff-add-strong": "var(--mint-dark-5)", - "surface-diff-add-stronger": "var(--mint-dark-11)", - "surface-diff-delete-base": "var(--ember-dark-3)", - "surface-diff-delete-weak": "var(--ember-dark-4)", - "surface-diff-delete-weaker": "var(--ember-dark-3)", - "surface-diff-delete-strong": "var(--ember-dark-5)", - "surface-diff-delete-stronger": "var(--ember-dark-11)", - "input-base": "#1c1c1c", - "input-hover": "#1c1c1c", - "input-active": "var(--cobalt-dark-1)", - "input-selected": "var(--cobalt-dark-2)", - "input-focus": "var(--cobalt-dark-1)", - "input-disabled": "#282828", - "text-base": "#ffffff96", - "text-weak": "#ffffff63", - "text-weaker": "#ffffff40", - "text-strong": "#ffffffeb", - "text-invert-base": "#ffffff96", - "text-invert-weak": "#ffffff63", - "text-invert-weaker": "#ffffff40", - "text-invert-strong": "#ffffffeb", - "text-interactive-base": "var(--cobalt-dark-11)", - "text-on-brand-base": "#ffffff96", - "text-on-interactive-base": "#ededed", - "text-on-interactive-weak": "#ffffff96", - "text-on-success-base": "var(--apple-dark-9)", - "text-on-critical-base": "var(--ember-dark-9)", - "text-on-critical-weak": "var(--ember-dark-8)", - "text-on-critical-strong": "var(--ember-dark-12)", - "text-on-warning-base": "#ffffff96", - "text-on-info-base": "#ffffff96", - "text-diff-add-base": "var(--mint-dark-11)", - "text-diff-delete-base": "var(--ember-dark-9)", - "text-diff-delete-strong": "var(--ember-dark-12)", - "text-diff-add-strong": "var(--mint-dark-8)", - "text-on-info-weak": "#ffffff63", - "text-on-info-strong": "#ffffffeb", - "text-on-warning-weak": "#ffffff63", - "text-on-warning-strong": "#ffffffeb", - "text-on-success-weak": "var(--apple-dark-8)", - "text-on-success-strong": "var(--apple-dark-12)", - "text-on-brand-weak": "#ffffff63", - "text-on-brand-weaker": "#ffffff40", - "text-on-brand-strong": "#ffffffeb", - "button-primary-base": "#ededed", - "button-secondary-base": "#1c1c1c", - "button-secondary-hover": "#FFFFFF0A", - "border-base": "#ffffff2b", - "border-hover": "#ffffff40", - "border-active": "#ffffff63", - "border-selected": "var(--cobalt-dark-alpha-11)", - "border-disabled": "#ffffff40", - "border-focus": "#ffffff63", - "border-weak-base": "#282828", - "border-weak-hover": "#ffffff2b", - "border-weak-active": "#ffffff40", - "border-weak-selected": "var(--cobalt-dark-alpha-6)", - "border-weak-disabled": "#ffffff21", - "border-weak-focus": "#ffffff40", - "border-strong-base": "#ffffff40", - "border-interactive-base": "var(--cobalt-light-7)", - "border-interactive-hover": "var(--cobalt-light-8)", - "border-interactive-active": "var(--cobalt-light-9)", - "border-interactive-selected": "var(--cobalt-light-9)", - "border-interactive-disabled": "#c7c7c7", - "border-interactive-focus": "var(--cobalt-light-9)", - "border-success-base": "var(--apple-light-6)", - "border-success-hover": "var(--apple-light-7)", - "border-success-selected": "var(--apple-light-9)", - "border-warning-base": "var(--solaris-light-6)", - "border-warning-hover": "var(--solaris-light-7)", - "border-warning-selected": "var(--solaris-light-9)", - "border-critical-base": "var(--ember-dark-5)", - "border-critical-hover": "var(--ember-dark-7)", - "border-critical-selected": "var(--ember-dark-9)", - "border-info-base": "var(--lilac-light-6)", - "border-info-hover": "var(--lilac-light-7)", - "border-info-selected": "var(--lilac-light-9)", - "icon-base": "#7e7e7e", - "icon-hover": "#a0a0a0", - "icon-active": "#ededed", - "icon-selected": "#ededed", - "icon-disabled": "#505050", - "icon-focus": "#ededed", - "icon-invert-base": "#161616", - "icon-weak-base": "#343434", - "icon-weak-hover": "#dbdbdb", - "icon-weak-active": "#c7c7c7", - "icon-weak-selected": "#8f8f8f", - "icon-weak-disabled": "#ededed", - "icon-weak-focus": "#8f8f8f", - "icon-strong-base": "#ededed", - "icon-strong-hover": "#F3F3F3", - "icon-strong-active": "#EBEBEB", - "icon-strong-selected": "#FCFCFC", - "icon-strong-disabled": "#3e3e3e", - "icon-strong-focus": "#FCFCFC", - "icon-brand-base": "var(--white)", - "icon-interactive-base": "var(--cobalt-dark-11)", - "icon-success-base": "var(--apple-dark-9)", - "icon-success-hover": "var(--apple-dark-10)", - "icon-success-active": "var(--apple-dark-11)", - "icon-warning-base": "var(--amber-dark-9)", - "icon-warning-hover": "var(--amber-dark-8)", - "icon-warning-active": "var(--amber-dark-11)", - "icon-critical-base": "var(--ember-dark-9)", - "icon-critical-hover": "var(--ember-dark-11)", - "icon-critical-active": "var(--ember-dark-12)", - "icon-info-base": "var(--lilac-dark-7)", - "icon-info-hover": "var(--lilac-dark-8)", - "icon-info-active": "var(--lilac-dark-11)", - "icon-on-brand-base": "#0000008f", - "icon-on-brand-hover": "#000000e8", - "icon-on-brand-selected": "#000000e8", - "icon-on-interactive-base": "#ededed", - "icon-agent-plan-base": "var(--purple-dark-9)", - "icon-agent-docs-base": "var(--amber-dark-9)", - "icon-agent-ask-base": "var(--cyan-dark-9)", - "icon-agent-build-base": "var(--cobalt-dark-11)", - "icon-on-success-base": "var(--apple-dark-alpha-9)", - "icon-on-success-hover": "var(--apple-dark-alpha-10)", - "icon-on-success-selected": "var(--apple-dark-alpha-11)", - "icon-on-warning-base": "var(--amber-darkalpha-9)", - "icon-on-warning-hover": "var(--amber-darkalpha-10)", - "icon-on-warning-selected": "var(--amber-darkalpha-11)", - "icon-on-critical-base": "var(--ember-dark-alpha-9)", - "icon-on-critical-hover": "var(--ember-dark-alpha-10)", - "icon-on-critical-selected": "var(--ember-dark-alpha-11)", - "icon-on-info-base": "var(--lilac-dark-9)", - "icon-on-info-hover": "var(--lilac-dark-alpha-10)", - "icon-on-info-selected": "var(--lilac-dark-alpha-11)", - "icon-diff-add-base": "var(--mint-dark-11)", - "icon-diff-add-hover": "var(--mint-dark-10)", - "icon-diff-add-active": "var(--mint-dark-11)", - "icon-diff-delete-base": "var(--ember-dark-9)", - "icon-diff-delete-hover": "var(--ember-dark-10)", - "syntax-comment": "var(--text-weak)", - "syntax-regexp": "var(--text-base)", - "syntax-string": "#00ceb9", - "syntax-keyword": "var(--text-weak)", - "syntax-primitive": "#ffba92", - "syntax-operator": "var(--text-weak)", - "syntax-variable": "var(--text-strong)", - "syntax-property": "#ff9ae2", - "syntax-type": "#ecf58c", - "syntax-constant": "#93e9f6", - "syntax-punctuation": "var(--text-weak)", - "syntax-object": "var(--text-strong)", - "syntax-success": "var(--apple-dark-10)", - "syntax-warning": "var(--amber-dark-10)", - "syntax-critical": "var(--ember-dark-10)", - "syntax-info": "#93e9f6", - "syntax-diff-add": "var(--mint-dark-11)", - "syntax-diff-delete": "var(--ember-dark-11)", - "syntax-diff-unknown": "#ff0000", - "markdown-heading": "#9d7cd8", - "markdown-text": "#eeeeee", - "markdown-link": "#fab283", - "markdown-link-text": "#56b6c2", - "markdown-code": "#7fd88f", - "markdown-block-quote": "#e5c07b", - "markdown-emph": "#e5c07b", - "markdown-strong": "#f5a742", - "markdown-horizontal-rule": "#808080", - "markdown-list-item": "#fab283", - "markdown-list-enumeration": "#56b6c2", - "markdown-image": "#fab283", - "markdown-image-text": "#56b6c2", - "markdown-code-block": "#eeeeee", - "border-color": "#ffffff", - "border-weaker-base": "#1e1e1e", - "button-ghost-hover": "#ffffff08", - "button-ghost-hover2": "#ffffff0f", - "avatar-background-pink": "#501b3f", - "avatar-background-mint": "#033a34", - "avatar-background-orange": "#5f2a06", - "avatar-background-purple": "#432155", - "avatar-background-cyan": "#0f3058", - "avatar-background-lime": "#2b3711", - "avatar-text-pink": "#e34ba9", - "avatar-text-mint": "#95f3d9", - "avatar-text-orange": "#ff802b", - "avatar-text-purple": "#9d5bd2", - "avatar-text-cyan": "#369eff", - "avatar-text-lime": "#c4f042" } } } diff --git a/packages/ui/src/theme/themes/onedarkpro.json b/packages/ui/src/theme/themes/onedarkpro.json index ce01511e85..be17dedff3 100644 --- a/packages/ui/src/theme/themes/onedarkpro.json +++ b/packages/ui/src/theme/themes/onedarkpro.json @@ -3,129 +3,39 @@ "name": "One Dark Pro", "id": "onedarkpro", "light": { - "seeds": { + "palette": { "neutral": "#f5f6f8", + "ink": "#2b303b", "primary": "#528bff", + "accent": "#d85462", "success": "#4fa66d", "warning": "#d19a66", "error": "#e06c75", "info": "#61afef", - "interactive": "#528bff", "diffAdd": "#c2ebcf", "diffDelete": "#f7c1c5" }, "overrides": { - "background-base": "#f5f6f8", - "background-weak": "#eef0f4", - "background-strong": "#fafbfc", - "background-stronger": "#ffffff", - "border-weak-base": "#dee2eb", - "border-weak-hover": "#d4d9e3", - "border-weak-active": "#caced6", - "border-weak-selected": "#bec4d0", - "border-weak-disabled": "#f4f6fb", - "border-weak-focus": "#c4cada", - "border-base": "#b5bccd", - "border-hover": "#aab1c2", - "border-active": "#a0a7b8", - "border-selected": "#959cae", - "border-disabled": "#eceef4", - "border-focus": "#a6adbf", - "border-strong-base": "#747c92", - "border-strong-hover": "#6a7287", - "border-strong-active": "#60687c", - "border-strong-selected": "#565e71", - "border-strong-disabled": "#cbd0dd", - "border-strong-focus": "#666d82", - "surface-diff-add-base": "#e5f4ea", - "surface-diff-delete-base": "#fde7ea", - "surface-diff-hidden-base": "#e4e8f4", - "text-base": "#2b303b", - "text-weak": "#6b717f", - "text-strong": "#0e1118", - "syntax-string": "#4fa66d", - "syntax-primitive": "#d85462", - "syntax-property": "#528bff", - "syntax-type": "#d19a66", - "syntax-constant": "#61afef", - "syntax-info": "#61afef", - "markdown-heading": "#528bff", - "markdown-text": "#2b303b", - "markdown-link": "#528bff", - "markdown-link-text": "#61afef", - "markdown-code": "#4fa66d", - "markdown-block-quote": "#d19a66", - "markdown-emph": "#d19a66", - "markdown-strong": "#d85462", - "markdown-horizontal-rule": "#d3d7e4", - "markdown-list-item": "#528bff", - "markdown-list-enumeration": "#61afef", - "markdown-image": "#528bff", - "markdown-image-text": "#61afef", - "markdown-code-block": "#528bff" + "syntax-keyword": "#a626a4", + "syntax-primitive": "#986801" } }, "dark": { - "seeds": { + "palette": { "neutral": "#1e222a", + "ink": "#abb2bf", "primary": "#61afef", + "accent": "#e06c75", "success": "#98c379", "warning": "#e5c07b", "error": "#e06c75", "info": "#56b6c2", - "interactive": "#61afef", "diffAdd": "#4b815a", "diffDelete": "#b2555f" }, "overrides": { - "background-base": "#1e222a", - "background-weak": "#212631", - "background-strong": "#1b1f27", - "background-stronger": "#171b23", - "border-weak-base": "#323848", - "border-weak-hover": "#363d52", - "border-weak-active": "#3c435c", - "border-weak-selected": "#424967", - "border-weak-disabled": "#141720", - "border-weak-focus": "#3f4560", - "border-base": "#4a5164", - "border-hover": "#515871", - "border-active": "#585f7e", - "border-selected": "#60688a", - "border-disabled": "#1a1e27", - "border-focus": "#555c79", - "border-strong-base": "#6a7390", - "border-strong-hover": "#737c9d", - "border-strong-active": "#7d87ab", - "border-strong-selected": "#8791b8", - "border-strong-disabled": "#212533", - "border-strong-focus": "#7680a2", - "surface-diff-add-base": "#1c2a26", - "surface-diff-delete-base": "#2a1c22", - "surface-diff-hidden-base": "#232836", - "text-base": "#abb2bf", - "text-weak": "#818899", - "text-strong": "#f6f7fb", - "syntax-string": "#98c379", - "syntax-primitive": "#e06c75", - "syntax-property": "#61afef", - "syntax-type": "#e5c07b", - "syntax-constant": "#56b6c2", - "syntax-info": "#56b6c2", - "markdown-heading": "#61afef", - "markdown-text": "#abb2bf", - "markdown-link": "#61afef", - "markdown-link-text": "#56b6c2", - "markdown-code": "#98c379", - "markdown-block-quote": "#e5c07b", - "markdown-emph": "#e5c07b", - "markdown-strong": "#e06c75", - "markdown-horizontal-rule": "#2d3444", - "markdown-list-item": "#61afef", - "markdown-list-enumeration": "#56b6c2", - "markdown-image": "#61afef", - "markdown-image-text": "#56b6c2", - "markdown-code-block": "#abb2bf" + "syntax-keyword": "#c678dd", + "syntax-primitive": "#d19a66" } } } diff --git a/packages/ui/src/theme/themes/shadesofpurple.json b/packages/ui/src/theme/themes/shadesofpurple.json index bc625770f9..03af35c2a3 100644 --- a/packages/ui/src/theme/themes/shadesofpurple.json +++ b/packages/ui/src/theme/themes/shadesofpurple.json @@ -3,129 +3,37 @@ "name": "Shades of Purple", "id": "shadesofpurple", "light": { - "seeds": { + "palette": { "neutral": "#f7ebff", + "ink": "#3b2c59", "primary": "#7a5af8", + "accent": "#ff6bd5", "success": "#3dd598", "warning": "#f7c948", "error": "#ff6bd5", "info": "#62d4ff", - "interactive": "#7a5af8", "diffAdd": "#c8f8da", "diffDelete": "#ffc3ef" }, "overrides": { - "background-base": "#f7ebff", - "background-weak": "#f2e2ff", - "background-strong": "#fbf2ff", - "background-stronger": "#fff7ff", - "border-weak-base": "#e5d3ff", - "border-weak-hover": "#dac8f5", - "border-weak-active": "#d1bdeb", - "border-weak-selected": "#c6b3e1", - "border-weak-disabled": "#fcf6ff", - "border-weak-focus": "#ccb9e7", - "border-base": "#baa4d5", - "border-hover": "#b098cb", - "border-active": "#a68dc2", - "border-selected": "#9b82b8", - "border-disabled": "#f1e7ff", - "border-focus": "#a692c6", - "border-strong-base": "#8769a9", - "border-strong-hover": "#7b5c9d", - "border-strong-active": "#704f91", - "border-strong-selected": "#664587", - "border-strong-disabled": "#d8c4f0", - "border-strong-focus": "#755495", - "surface-diff-add-base": "#edf8f1", - "surface-diff-delete-base": "#ffe4f4", - "surface-diff-hidden-base": "#e9e4ff", - "text-base": "#3b2c59", - "text-weak": "#6c568f", - "text-strong": "#1c1033", - "syntax-string": "#3dd598", - "syntax-primitive": "#ff6bd5", - "syntax-property": "#7a5af8", - "syntax-type": "#f7c948", - "syntax-constant": "#62d4ff", - "syntax-info": "#62d4ff", - "markdown-heading": "#7a5af8", - "markdown-text": "#3b2c59", - "markdown-link": "#7a5af8", - "markdown-link-text": "#62d4ff", - "markdown-code": "#3dd598", - "markdown-block-quote": "#f7c948", - "markdown-emph": "#f7c948", - "markdown-strong": "#ff6bd5", - "markdown-horizontal-rule": "#decbed", - "markdown-list-item": "#7a5af8", - "markdown-list-enumeration": "#62d4ff", - "markdown-image": "#7a5af8", - "markdown-image-text": "#62d4ff", - "markdown-code-block": "#7a5af8" + "syntax-keyword": "#ff6bd5" } }, "dark": { - "seeds": { + "palette": { "neutral": "#1a102b", + "ink": "#f5f0ff", "primary": "#c792ff", + "accent": "#ff7ac6", "success": "#7be0b0", "warning": "#ffd580", "error": "#ff7ac6", "info": "#7dd4ff", - "interactive": "#c792ff", "diffAdd": "#53c39f", "diffDelete": "#d85aa0" }, "overrides": { - "background-base": "#1a102b", - "background-weak": "#1f1434", - "background-strong": "#1c122f", - "background-stronger": "#170e26", - "border-weak-base": "#352552", - "border-weak-hover": "#3a2a5d", - "border-weak-active": "#402f68", - "border-weak-selected": "#463674", - "border-weak-disabled": "#10091b", - "border-weak-focus": "#3d2d65", - "border-base": "#4d3a73", - "border-hover": "#553f7f", - "border-active": "#5d468c", - "border-selected": "#654c99", - "border-disabled": "#150d21", - "border-focus": "#594283", - "border-strong-base": "#7659b0", - "border-strong-hover": "#8262be", - "border-strong-active": "#8e6ccc", - "border-strong-selected": "#9a77da", - "border-strong-disabled": "#1c122c", - "border-strong-focus": "#8666c4", - "surface-diff-add-base": "#142c27", - "surface-diff-delete-base": "#2d1424", - "surface-diff-hidden-base": "#231737", - "text-base": "#f5f0ff", - "text-weak": "#c9b6ff", - "text-strong": "#ffffff", - "syntax-string": "#7be0b0", - "syntax-primitive": "#ff7ac6", - "syntax-property": "#c792ff", - "syntax-type": "#ffd580", - "syntax-constant": "#7dd4ff", - "syntax-info": "#7dd4ff", - "markdown-heading": "#c792ff", - "markdown-text": "#f5f0ff", - "markdown-link": "#c792ff", - "markdown-link-text": "#7dd4ff", - "markdown-code": "#7be0b0", - "markdown-block-quote": "#ffd580", - "markdown-emph": "#ffd580", - "markdown-strong": "#ff7ac6", - "markdown-horizontal-rule": "#2d1d41", - "markdown-list-item": "#c792ff", - "markdown-list-enumeration": "#7dd4ff", - "markdown-image": "#c792ff", - "markdown-image-text": "#7dd4ff", - "markdown-code-block": "#f5f0ff" + "syntax-keyword": "#ff7ac6" } } } diff --git a/packages/ui/src/theme/themes/solarized.json b/packages/ui/src/theme/themes/solarized.json index 7cb44775af..24a4daf458 100644 --- a/packages/ui/src/theme/themes/solarized.json +++ b/packages/ui/src/theme/themes/solarized.json @@ -3,129 +3,39 @@ "name": "Solarized", "id": "solarized", "light": { - "seeds": { + "palette": { "neutral": "#fdf6e3", + "ink": "#586e75", "primary": "#268bd2", + "accent": "#d33682", "success": "#859900", "warning": "#b58900", "error": "#dc322f", "info": "#2aa198", - "interactive": "#268bd2", "diffAdd": "#c6dc7a", "diffDelete": "#f2a1a1" }, "overrides": { - "background-base": "#fdf6e3", - "background-weak": "#f6efda", - "background-strong": "#faf3dc", - "background-stronger": "#f6edd4", - "border-weak-base": "#e3e0cd", - "border-weak-hover": "#d9d4c2", - "border-weak-active": "#cfcab7", - "border-weak-selected": "#c5c0ad", - "border-weak-disabled": "#f2edda", - "border-weak-focus": "#cbc6b2", - "border-base": "#bcb5a0", - "border-hover": "#b1aa96", - "border-active": "#a59f8c", - "border-selected": "#999382", - "border-disabled": "#ede7d4", - "border-focus": "#aca58f", - "border-strong-base": "#8c8572", - "border-strong-hover": "#7f7866", - "border-strong-active": "#716b5b", - "border-strong-selected": "#645f50", - "border-strong-disabled": "#d5cdb8", - "border-strong-focus": "#78715f", - "surface-diff-add-base": "#eef5d6", - "surface-diff-delete-base": "#fde4dd", - "surface-diff-hidden-base": "#e3ecf3", - "text-base": "#586e75", - "text-weak": "#7a8c8e", - "text-strong": "#073642", - "syntax-string": "#859900", - "syntax-primitive": "#d33682", - "syntax-property": "#268bd2", - "syntax-type": "#b58900", - "syntax-constant": "#2aa198", - "syntax-info": "#2aa198", - "markdown-heading": "#268bd2", - "markdown-text": "#586e75", - "markdown-link": "#268bd2", - "markdown-link-text": "#2aa198", - "markdown-code": "#859900", - "markdown-block-quote": "#b58900", - "markdown-emph": "#b58900", - "markdown-strong": "#d33682", - "markdown-horizontal-rule": "#cfd1bf", - "markdown-list-item": "#268bd2", - "markdown-list-enumeration": "#2aa198", - "markdown-image": "#268bd2", - "markdown-image-text": "#2aa198", - "markdown-code-block": "#2aa198" + "syntax-keyword": "#859900", + "syntax-string": "#2aa198" } }, "dark": { - "seeds": { + "palette": { "neutral": "#002b36", + "ink": "#93a1a1", "primary": "#6c71c4", + "accent": "#d33682", "success": "#859900", "warning": "#b58900", "error": "#dc322f", "info": "#2aa198", - "interactive": "#6c71c4", "diffAdd": "#4c7654", "diffDelete": "#c34b4b" }, "overrides": { - "background-base": "#001f27", - "background-weak": "#022733", - "background-strong": "#01222b", - "background-stronger": "#032830", - "border-weak-base": "#20373f", - "border-weak-hover": "#243e47", - "border-weak-active": "#28434f", - "border-weak-selected": "#2d4958", - "border-weak-disabled": "#0f2026", - "border-weak-focus": "#2a4552", - "border-base": "#31505b", - "border-hover": "#365765", - "border-active": "#3c5e70", - "border-selected": "#42657a", - "border-disabled": "#13272e", - "border-focus": "#3a5a6b", - "border-strong-base": "#4a7887", - "border-strong-hover": "#528294", - "border-strong-active": "#5a8ca1", - "border-strong-selected": "#6396ae", - "border-strong-disabled": "#1b323b", - "border-strong-focus": "#56879a", - "surface-diff-add-base": "#0f2f29", - "surface-diff-delete-base": "#321c1c", - "surface-diff-hidden-base": "#0f3844", - "text-base": "#93a1a1", - "text-weak": "#6c7f80", - "text-strong": "#fdf6e3", - "syntax-string": "#859900", - "syntax-primitive": "#d33682", - "syntax-property": "#6c71c4", - "syntax-type": "#b58900", - "syntax-constant": "#2aa198", - "syntax-info": "#2aa198", - "markdown-heading": "#6c71c4", - "markdown-text": "#93a1a1", - "markdown-link": "#6c71c4", - "markdown-link-text": "#2aa198", - "markdown-code": "#859900", - "markdown-block-quote": "#b58900", - "markdown-emph": "#b58900", - "markdown-strong": "#d33682", - "markdown-horizontal-rule": "#0e3b46", - "markdown-list-item": "#6c71c4", - "markdown-list-enumeration": "#2aa198", - "markdown-image": "#6c71c4", - "markdown-image-text": "#2aa198", - "markdown-code-block": "#93a1a1" + "syntax-keyword": "#859900", + "syntax-string": "#2aa198" } } } diff --git a/packages/ui/src/theme/themes/tokyonight.json b/packages/ui/src/theme/themes/tokyonight.json index 31d0e8a474..d29c359942 100644 --- a/packages/ui/src/theme/themes/tokyonight.json +++ b/packages/ui/src/theme/themes/tokyonight.json @@ -3,153 +3,37 @@ "name": "Tokyonight", "id": "tokyonight", "light": { - "seeds": { + "palette": { "neutral": "#e1e2e7", + "ink": "#273153", "primary": "#2e7de9", + "accent": "#b15c00", "success": "#587539", "warning": "#8c6c3e", "error": "#c94060", "info": "#007197", - "interactive": "#2e7de9", "diffAdd": "#4f8f7b", "diffDelete": "#d05f7c" }, "overrides": { - "background-base": "#e1e2e7", - "background-weak": "#dee0ea", - "background-strong": "#e5e6ee", - "background-stronger": "#e9eaf1", - "border-weak-base": "#cdd0dc", - "border-weak-hover": "#c3c6d2", - "border-weak-active": "#b9bcc8", - "border-weak-selected": "#aeb2bf", - "border-weak-disabled": "#e6e7ef", - "border-weak-focus": "#b3b6c3", - "border-base": "#a7abbb", - "border-hover": "#9ba0b1", - "border-active": "#9095a8", - "border-selected": "#83889e", - "border-disabled": "#dedfe6", - "border-focus": "#9599a8", - "border-strong-base": "#757b90", - "border-strong-hover": "#6a7084", - "border-strong-active": "#5f6578", - "border-strong-selected": "#545a6d", - "border-strong-disabled": "#c4c6d0", - "border-strong-focus": "#666b7f", - "surface-diff-add-base": "#dfe7da", - "surface-diff-delete-base": "#f4dadd", - "surface-diff-hidden-base": "#cfd1dd", - "text-base": "#273153", - "text-weak": "#5c6390", - "text-strong": "#1c2544", - "syntax-string": "#587539", - "syntax-primitive": "#b15c00", - "syntax-property": "#9854f1", - "syntax-type": "#3760bf", - "syntax-constant": "#007197", - "syntax-info": "#007197", - "markdown-heading": "#9854f1", - "markdown-text": "#273153", - "markdown-link": "#2e7de9", - "markdown-link-text": "#007197", - "markdown-code": "#587539", - "markdown-block-quote": "#8c6c3e", - "markdown-emph": "#8c6c3e", - "markdown-strong": "#b15c00", - "markdown-horizontal-rule": "#a1a6c5", - "markdown-list-item": "#2e7de9", - "markdown-list-enumeration": "#007197", - "markdown-image": "#2e7de9", - "markdown-image-text": "#007197", - "markdown-code-block": "#3760bf" + "syntax-keyword": "#9854f1" } }, "dark": { - "seeds": { + "palette": { "neutral": "#1a1b26", + "ink": "#c0caf5", "primary": "#7aa2f7", + "accent": "#ff9e64", "success": "#9ece6a", "warning": "#e0af68", "error": "#f7768e", "info": "#7dcfff", - "interactive": "#7aa2f7", "diffAdd": "#41a6b5", "diffDelete": "#c34043" }, "overrides": { - "background-base": "#0f111a", - "background-weak": "#111428", - "background-strong": "#101324", - "background-stronger": "#13172a", - "border-weak-base": "#25283b", - "border-weak-hover": "#292c43", - "border-weak-active": "#2e314b", - "border-weak-selected": "#343755", - "border-weak-disabled": "#151727", - "border-weak-focus": "#30324f", - "border-base": "#3a3e57", - "border-hover": "#414264", - "border-active": "#474972", - "border-selected": "#4f507f", - "border-disabled": "#1c1d2d", - "border-focus": "#45496f", - "border-strong-base": "#5a5f82", - "border-strong-hover": "#646994", - "border-strong-active": "#6f74a6", - "border-strong-selected": "#7a7fb8", - "border-strong-disabled": "#23243a", - "border-strong-focus": "#6a6f9f", - "surface-base": "#1f2335", - "base": "#1f2335", - "surface-base-hover": "#232840", - "surface-base-active": "#262c46", - "surface-base-interactive-active": "#2b3357", - "base2": "#1f2335", - "base3": "#1f2335", - "surface-inset-base": "#161a2ab3", - "surface-inset-base-hover": "#161a2acc", - "surface-inset-strong": "#0d111fcc", - "surface-inset-strong-hover": "#0d111fcc", - "surface-raised-base": "#242a42", - "surface-float-base": "#242b45", - "surface-float-base-hover": "#2a3154", - "surface-raised-base-hover": "#272e49", - "surface-raised-base-active": "#2c3353", - "surface-raised-strong": "#31385a", - "surface-raised-strong-hover": "#373f6b", - "surface-raised-stronger": "#3b4261", - "surface-raised-stronger-hover": "#444c82", - "surface-weak": "#1b2033", - "surface-weaker": "#181d2d", - "surface-strong": "#323858", - "surface-raised-stronger-non-alpha": "#2b3150", - "surface-diff-add-base": "#1c2a38", - "surface-diff-delete-base": "#2a1f32", - "surface-diff-hidden-base": "#24283b", - "text-base": "#c0caf5", - "text-weak": "#7a88cf", - "text-strong": "#eaeaff", - "syntax-string": "#9ece6a", - "syntax-primitive": "#ff9e64", - "syntax-property": "#bb9af7", - "syntax-type": "#e0af68", - "syntax-constant": "#7dcfff", - "syntax-info": "#7dcfff", - "markdown-heading": "#bb9af7", - "markdown-text": "#c0caf5", - "markdown-link": "#7aa2f7", - "markdown-link-text": "#7dcfff", - "markdown-code": "#9ece6a", - "markdown-block-quote": "#e0af68", - "markdown-emph": "#e0af68", - "markdown-strong": "#ff9e64", - "markdown-horizontal-rule": "#3b4261", - "markdown-list-item": "#7aa2f7", - "markdown-list-enumeration": "#7dcfff", - "markdown-image": "#7aa2f7", - "markdown-image-text": "#7dcfff", - "markdown-code-block": "#c0caf5" + "syntax-keyword": "#bb9af7" } } } diff --git a/packages/ui/src/theme/themes/vesper.json b/packages/ui/src/theme/themes/vesper.json index 040bdc049b..8cc658232f 100644 --- a/packages/ui/src/theme/themes/vesper.json +++ b/packages/ui/src/theme/themes/vesper.json @@ -3,129 +3,38 @@ "name": "Vesper", "id": "vesper", "light": { - "seeds": { + "palette": { "neutral": "#F0F0F0", + "ink": "#101010", "primary": "#FFC799", + "accent": "#B30000", "success": "#99FFE4", "warning": "#FFC799", "error": "#FF8080", "info": "#FFC799", - "interactive": "#FFC799", "diffAdd": "#99FFE4", "diffDelete": "#FF8080" }, "overrides": { - "background-base": "#FFF", - "background-weak": "#F8F8F8", - "background-strong": "#F0F0F0", - "background-stronger": "#FBFBFB", - "border-weak-hover": "#E0E0E0", - "border-weak-active": "#D8D8D8", - "border-weak-selected": "#D0D0D0", - "border-weak-disabled": "#F0F0F0", - "border-weak-focus": "#D8D8D8", - "border-base": "#D0D0D0", - "border-hover": "#C8C8C8", - "border-active": "#C0C0C0", - "border-selected": "#B8B8B8", - "border-disabled": "#E8E8E8", - "border-focus": "#C0C0C0", - "border-strong-base": "#A0A0A0", - "border-strong-hover": "#989898", - "border-strong-active": "#909090", - "border-strong-selected": "#888888", - "border-strong-disabled": "#D0D0D0", - "border-strong-focus": "#909090", - "surface-diff-add-base": "#e8f5e8", - "surface-diff-delete-base": "#f5e8e8", - "surface-diff-hidden-base": "#F0F0F0", - "text-base": "#101010", - "text-invert-strong": "var(--smoke-dark-alpha-12)", - "text-weak": "#606060", - "text-strong": "#000000", - "syntax-string": "#0D5C4F", - "syntax-primitive": "#B30000", - "syntax-property": "#C66C00", - "syntax-type": "#9C5C12", - "syntax-constant": "#404040", - "syntax-info": "#606060", - "markdown-heading": "#FFC799", - "markdown-text": "#101010", - "markdown-link": "#FFC799", - "markdown-link-text": "#A0A0A0", - "markdown-code": "#A0A0A0", - "markdown-block-quote": "#101010", - "markdown-emph": "#101010", - "markdown-strong": "#101010", - "markdown-horizontal-rule": "#65737E", - "markdown-list-item": "#101010", - "markdown-list-enumeration": "#101010", - "markdown-image": "#FFC799", - "markdown-image-text": "#A0A0A0", - "markdown-code-block": "#FFC799" + "syntax-keyword": "#b30000" } }, "dark": { - "seeds": { + "palette": { "neutral": "#101010", + "ink": "#FFF", "primary": "#FFC799", + "accent": "#FF8080", "success": "#99FFE4", "warning": "#FFC799", "error": "#FF8080", "info": "#FFC799", - "interactive": "#FFC799", "diffAdd": "#99FFE4", "diffDelete": "#FF8080" }, "overrides": { - "background-base": "#101010", - "background-weak": "#141414", - "background-strong": "#0C0C0C", - "background-stronger": "#080808", - "border-weak-base": "#1C1C1C", - "border-weak-hover": "#202020", - "border-weak-active": "#242424", - "border-weak-selected": "#282828", - "border-weak-disabled": "#141414", - "border-weak-focus": "#242424", - "border-base": "#282828", - "border-hover": "#303030", - "border-active": "#383838", - "border-selected": "#404040", - "border-disabled": "#181818", - "border-focus": "#383838", - "border-strong-base": "#505050", - "border-strong-hover": "#585858", - "border-strong-active": "#606060", - "border-strong-selected": "#686868", - "border-strong-disabled": "#202020", - "border-strong-focus": "#606060", - "surface-diff-add-base": "#0d2818", - "surface-diff-delete-base": "#281a1a", - "surface-diff-hidden-base": "#141414", - "text-base": "#FFF", - "text-weak": "#A0A0A0", - "text-strong": "#FFFFFF", - "syntax-string": "#99FFE4", - "syntax-primitive": "#FF8080", - "syntax-property": "#FFC799", - "syntax-type": "#FFC799", - "syntax-constant": "#A0A0A0", - "syntax-info": "#8b8b8b", - "markdown-heading": "#FFC799", - "markdown-text": "#FFF", - "markdown-link": "#FFC799", - "markdown-link-text": "#A0A0A0", - "markdown-code": "#A0A0A0", - "markdown-block-quote": "#FFF", - "markdown-emph": "#FFF", - "markdown-strong": "#FFF", - "markdown-horizontal-rule": "#65737E", - "markdown-list-item": "#FFF", - "markdown-list-enumeration": "#FFF", - "markdown-image": "#FFC799", - "markdown-image-text": "#A0A0A0", - "markdown-code-block": "#FFF" + "syntax-keyword": "#ff8080", + "syntax-primitive": "#ffc799" } } } diff --git a/packages/ui/src/theme/types.ts b/packages/ui/src/theme/types.ts index 73bd372b45..126e9bb5aa 100644 --- a/packages/ui/src/theme/types.ts +++ b/packages/ui/src/theme/types.ts @@ -18,11 +18,28 @@ export interface ThemeSeedColors { diffDelete: HexColor } -export interface ThemeVariant { - seeds: ThemeSeedColors +export interface ThemePaletteColors { + neutral: HexColor + ink?: HexColor + primary: HexColor + success: HexColor + warning: HexColor + error: HexColor + info: HexColor + accent?: HexColor + interactive?: HexColor + diffAdd?: HexColor + diffDelete?: HexColor +} + +type ThemeVariantBase = { overrides?: Record } +export type ThemeVariant = + | ({ seeds: ThemeSeedColors; palette?: never } & ThemeVariantBase) + | ({ palette: ThemePaletteColors; seeds?: never } & ThemeVariantBase) + export interface DesktopTheme { $schema?: string name: string From 656a8d8f55ad02fd7f849f98dcc4bf09683649b0 Mon Sep 17 00:00:00 2001 From: Ariane Emory <97994360+ariane-emory@users.noreply.github.com> Date: Sun, 8 Mar 2026 22:03:52 -0400 Subject: [PATCH 003/200] docs: add session_child_first keybinding to documentation (#16631) --- packages/web/src/content/docs/keybinds.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/content/docs/keybinds.mdx b/packages/web/src/content/docs/keybinds.mdx index 95b3d49639..54c15e8621 100644 --- a/packages/web/src/content/docs/keybinds.mdx +++ b/packages/web/src/content/docs/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode has a list of keybinds that you can customize through `tui.json`. "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", From 849e1ac54378ec3b6452ca589266b519fa17ba23 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 9 Mar 2026 02:08:46 +0000 Subject: [PATCH 004/200] docs(i18n): sync locale docs from english changes --- packages/web/src/content/docs/ar/keybinds.mdx | 1 + packages/web/src/content/docs/bs/keybinds.mdx | 1 + packages/web/src/content/docs/da/keybinds.mdx | 1 + packages/web/src/content/docs/de/keybinds.mdx | 13 ++++---- packages/web/src/content/docs/es/keybinds.mdx | 1 + packages/web/src/content/docs/fr/keybinds.mdx | 1 + packages/web/src/content/docs/it/keybinds.mdx | 1 + packages/web/src/content/docs/ja/keybinds.mdx | 1 + packages/web/src/content/docs/ko/keybinds.mdx | 1 + packages/web/src/content/docs/nb/keybinds.mdx | 1 + packages/web/src/content/docs/pl/keybinds.mdx | 1 + .../web/src/content/docs/pt-br/keybinds.mdx | 1 + packages/web/src/content/docs/ru/keybinds.mdx | 1 + packages/web/src/content/docs/th/keybinds.mdx | 1 + packages/web/src/content/docs/tr/keybinds.mdx | 33 ++++++++++--------- .../web/src/content/docs/zh-cn/keybinds.mdx | 1 + .../web/src/content/docs/zh-tw/keybinds.mdx | 1 + 17 files changed, 39 insertions(+), 22 deletions(-) diff --git a/packages/web/src/content/docs/ar/keybinds.mdx b/packages/web/src/content/docs/ar/keybinds.mdx index f07eaed37b..986313a5b5 100644 --- a/packages/web/src/content/docs/ar/keybinds.mdx +++ b/packages/web/src/content/docs/ar/keybinds.mdx @@ -28,6 +28,7 @@ description: خصّص اختصارات لوحة المفاتيح. "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/bs/keybinds.mdx b/packages/web/src/content/docs/bs/keybinds.mdx index a7a6d34a0d..a70fdedaef 100644 --- a/packages/web/src/content/docs/bs/keybinds.mdx +++ b/packages/web/src/content/docs/bs/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode ima listu veza tipki koje možete prilagoditi putem `tui.json`. "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/da/keybinds.mdx b/packages/web/src/content/docs/da/keybinds.mdx index 9c066fcb49..237c36f775 100644 --- a/packages/web/src/content/docs/da/keybinds.mdx +++ b/packages/web/src/content/docs/da/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode har en liste over nøglebindinger, som du kan tilpasse gennem `tui.json "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/de/keybinds.mdx b/packages/web/src/content/docs/de/keybinds.mdx index d575800805..628c65006d 100644 --- a/packages/web/src/content/docs/de/keybinds.mdx +++ b/packages/web/src/content/docs/de/keybinds.mdx @@ -3,11 +3,11 @@ title: Tastenkombinationen description: Passen Sie Ihre Tastenkombinationen an. --- -OpenCode verfügt über eine Liste von Tastenkombinationen, die Sie über die OpenCode-Konfiguration anpassen können. +OpenCode verfügt über eine Liste von Tastenkombinationen, die Sie über `tui.json` anpassen können. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "leader": "ctrl+x", "app_exit": "ctrl+c,ctrl+d,q", @@ -28,6 +28,7 @@ OpenCode verfügt über eine Liste von Tastenkombinationen, die Sie über die Op "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", @@ -117,11 +118,11 @@ Sie müssen für Ihre Keybinds keinen Leader Key verwenden, wir empfehlen jedoch ## Keybind deaktivieren -Sie können eine Keybind deaktivieren, indem Sie den Schlüssel mit dem Wert „none“ zu Ihrer Konfiguration hinzufügen. +Sie können eine Keybind deaktivieren, indem Sie den Schlüssel mit dem Wert „none“ zu `tui.json` hinzufügen. -```json title="opencode.json" +```json title="tui.json" { - "$schema": "https://opencode.ai/config.json", + "$schema": "https://opencode.ai/tui.json", "keybinds": { "session_compact": "none" } diff --git a/packages/web/src/content/docs/es/keybinds.mdx b/packages/web/src/content/docs/es/keybinds.mdx index d4880db2fa..4c1f7e1a80 100644 --- a/packages/web/src/content/docs/es/keybinds.mdx +++ b/packages/web/src/content/docs/es/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode tiene una lista de combinaciones de teclas que puede personalizar a tra "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/fr/keybinds.mdx b/packages/web/src/content/docs/fr/keybinds.mdx index 4ec98adfa2..281e5df743 100644 --- a/packages/web/src/content/docs/fr/keybinds.mdx +++ b/packages/web/src/content/docs/fr/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode a une liste de raccourcis clavier que vous pouvez personnaliser via la "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/it/keybinds.mdx b/packages/web/src/content/docs/it/keybinds.mdx index 548805e6ba..e599f4e417 100644 --- a/packages/web/src/content/docs/it/keybinds.mdx +++ b/packages/web/src/content/docs/it/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode ha una lista di scorciatoie che puoi personalizzare tramite `tui.json`. "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/ja/keybinds.mdx b/packages/web/src/content/docs/ja/keybinds.mdx index 09a3b0d7cb..3ec9ca94d5 100644 --- a/packages/web/src/content/docs/ja/keybinds.mdx +++ b/packages/web/src/content/docs/ja/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode には、`tui.json` を通じてカスタマイズできるキーバイ "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/ko/keybinds.mdx b/packages/web/src/content/docs/ko/keybinds.mdx index 2920a22357..fa8b4e9bdd 100644 --- a/packages/web/src/content/docs/ko/keybinds.mdx +++ b/packages/web/src/content/docs/ko/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode에는 `tui.json`을 통해 커스터마이즈할 수 있는 키바인 "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/nb/keybinds.mdx b/packages/web/src/content/docs/nb/keybinds.mdx index 8314b4dd82..f9837480dc 100644 --- a/packages/web/src/content/docs/nb/keybinds.mdx +++ b/packages/web/src/content/docs/nb/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode har en liste over tastebindinger som du kan tilpasse gjennom `tui.json` "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/pl/keybinds.mdx b/packages/web/src/content/docs/pl/keybinds.mdx index 03b9ec9c2a..4744ffc783 100644 --- a/packages/web/src/content/docs/pl/keybinds.mdx +++ b/packages/web/src/content/docs/pl/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode zawiera listę skrótów klawiszowych, które można dostosować za pom "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/pt-br/keybinds.mdx b/packages/web/src/content/docs/pt-br/keybinds.mdx index 1829763ade..6c7fcd208e 100644 --- a/packages/web/src/content/docs/pt-br/keybinds.mdx +++ b/packages/web/src/content/docs/pt-br/keybinds.mdx @@ -28,6 +28,7 @@ O opencode tem uma lista de atalhos de teclado que você pode personalizar atrav "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/ru/keybinds.mdx b/packages/web/src/content/docs/ru/keybinds.mdx index 8a9a14ca1a..bfd4bf0c24 100644 --- a/packages/web/src/content/docs/ru/keybinds.mdx +++ b/packages/web/src/content/docs/ru/keybinds.mdx @@ -28,6 +28,7 @@ opencode имеет список сочетаний клавиш, которые "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/th/keybinds.mdx b/packages/web/src/content/docs/th/keybinds.mdx index 8cc7586e5f..ce3234a04a 100644 --- a/packages/web/src/content/docs/th/keybinds.mdx +++ b/packages/web/src/content/docs/th/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode มีรายการปุ่มลัดที่คุณปร "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/tr/keybinds.mdx b/packages/web/src/content/docs/tr/keybinds.mdx index bea63a3550..7d3142bf38 100644 --- a/packages/web/src/content/docs/tr/keybinds.mdx +++ b/packages/web/src/content/docs/tr/keybinds.mdx @@ -28,6 +28,7 @@ opencode, `tui.json` aracılığıyla özelleştirebileceğiniz bir tuş bağlan "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", @@ -134,21 +135,21 @@ Anahtarı yapılandırmanıza "none" değeriyle ekleyerek bir tuş atamasını d opencode masaüstü uygulaması bilgi istemi girişi, metni düzenlemek için yaygın Readline/Emacs tarzı kısayolları destekler. Bunlar yerleşiktir ve şu anda `opencode.json` aracılığıyla yapılandırılamaz. -| Shortcut | Action | -| -------- | ---------------------------------------- | -| `ctrl+a` | Geçerli satırın başına git | -| `ctrl+e` | Move to end of current line | -| `ctrl+b` | Move cursor back one character | -| `ctrl+f` | Move cursor forward one character | -| `alt+b` | Move cursor back one word | -| `alt+f` | Move cursor forward one word | -| `ctrl+d` | Delete character under cursor | -| `ctrl+k` | Kill to end of line | -| `ctrl+u` | Satırın başına kadar öldür | -| `ctrl+w` | Kill previous word | -| `alt+d` | Kill next word | -| `ctrl+t` | Transpose characters | -| `ctrl+g` | Cancel popovers / abort running response | +| Shortcut | Action | +| -------- | --------------------------------------------------- | +| `ctrl+a` | Geçerli satırın başına git | +| `ctrl+e` | Geçerli satırın sonuna git | +| `ctrl+b` | İmleci bir karakter geri taşı | +| `ctrl+f` | İmleci bir karakter ileri taşı | +| `alt+b` | İmleci bir kelime geri taşı | +| `alt+f` | İmleci bir kelime ileri taşı | +| `ctrl+d` | İmleç altındaki karakteri sil | +| `ctrl+k` | Satırın sonuna kadar sil | +| `ctrl+u` | Satırın başına kadar sil | +| `ctrl+w` | Önceki kelimeyi sil | +| `alt+d` | Sonraki kelimeyi sil | +| `ctrl+t` | Karakterlerin yerini değiştir | +| `ctrl+g` | Açılır pencereleri iptal et / çalışan yanıtı durdur | --- @@ -158,7 +159,7 @@ Bazı terminaller varsayılan olarak Enter ile değiştirici tuşlar göndermez. ### Windows Terminali -`settings.json` cihazınızı şu adreste açın: +`settings.json` dosyasını şurada açın: ``` %LOCALAPPDATA%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json diff --git a/packages/web/src/content/docs/zh-cn/keybinds.mdx b/packages/web/src/content/docs/zh-cn/keybinds.mdx index 5108fdbb51..33f75c6dc8 100644 --- a/packages/web/src/content/docs/zh-cn/keybinds.mdx +++ b/packages/web/src/content/docs/zh-cn/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode 提供了一系列快捷键,您可以通过 `tui.json` 进行自定 "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", diff --git a/packages/web/src/content/docs/zh-tw/keybinds.mdx b/packages/web/src/content/docs/zh-tw/keybinds.mdx index ca085db01d..574404b2fd 100644 --- a/packages/web/src/content/docs/zh-tw/keybinds.mdx +++ b/packages/web/src/content/docs/zh-tw/keybinds.mdx @@ -28,6 +28,7 @@ OpenCode 提供了一系列快捷鍵,您可以透過 `tui.json` 進行自訂 "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", + "session_child_first": "down", "session_child_cycle": "right", "session_child_cycle_reverse": "left", "session_parent": "up", From 18fb19da3b5aad7e05af9ebdfb881704319853a7 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Mon, 9 Mar 2026 02:32:13 -0500 Subject: [PATCH 005/200] fix(opencode): pass missing auth headers in `run --attach` (#16097) Co-authored-by: Claude Opus 4.6 Co-authored-by: Shoubhit Dash --- packages/opencode/src/cli/cmd/run.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 61bc609bb7..5c6dd11f99 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -280,6 +280,11 @@ export const RunCommand = cmd({ type: "string", describe: "attach to a running opencode server (e.g., http://localhost:4096)", }) + .option("password", { + alias: ["p"], + type: "string", + describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)", + }) .option("dir", { type: "string", describe: "directory to run in, path on remote server if attaching", @@ -648,7 +653,14 @@ export const RunCommand = cmd({ } if (args.attach) { - const sdk = createOpencodeClient({ baseUrl: args.attach, directory }) + const headers = (() => { + const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD + if (!password) return undefined + const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode" + const auth = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}` + return { Authorization: auth } + })() + const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers }) return await execute(sdk) } From 34328828ae9d28afe28202d8122a6983a6b2dc0f Mon Sep 17 00:00:00 2001 From: Yihui Khuu Date: Mon, 9 Mar 2026 20:59:24 +1100 Subject: [PATCH 006/200] fix(app): fix issue with scroll jumping when pressing escape in comment text area (#15374) --- packages/ui/src/components/line-comment.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/components/line-comment.tsx b/packages/ui/src/components/line-comment.tsx index 73d83f7d72..ff5d1df007 100644 --- a/packages/ui/src/components/line-comment.tsx +++ b/packages/ui/src/components/line-comment.tsx @@ -244,6 +244,7 @@ export const LineCommentEditor = (props: LineCommentEditorProps) => { event.stopPropagation() if (e.key === "Escape") { event.preventDefault() + e.currentTarget.blur() split.onCancel() return } From f27ef595f65aa719be3f8d08665d683e95083ed3 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:26:53 +1000 Subject: [PATCH 007/200] fix(app): sanitize workspace store filenames on Windows (#16703) --- packages/app/e2e/utils.ts | 2 +- packages/app/src/utils/persist.test.ts | 8 ++++++++ packages/app/src/utils/persist.ts | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/app/e2e/utils.ts b/packages/app/e2e/utils.ts index 0dbc5f8b5a..f07a8d3f11 100644 --- a/packages/app/e2e/utils.ts +++ b/packages/app/e2e/utils.ts @@ -57,7 +57,7 @@ export function sessionPath(directory: string, sessionID?: string) { } export function workspacePersistKey(directory: string, key: string) { - const head = directory.slice(0, 12) || "workspace" + const head = (directory.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-") const sum = checksum(directory) ?? "0" return `opencode.workspace.${head}.${sum}.dat:workspace:${key}` } diff --git a/packages/app/src/utils/persist.test.ts b/packages/app/src/utils/persist.test.ts index 2a2c349b75..673acd224d 100644 --- a/packages/app/src/utils/persist.test.ts +++ b/packages/app/src/utils/persist.test.ts @@ -104,4 +104,12 @@ describe("persist localStorage resilience", () => { const result = persistTesting.normalize({ value: "ok" }, '{"value":"\\x"}') expect(result).toBeUndefined() }) + + test("workspace storage sanitizes Windows filename characters", () => { + const result = persistTesting.workspaceStorage("C:\\Users\\foo") + + expect(result).toStartWith("opencode.workspace.") + expect(result.endsWith(".dat")).toBeTrue() + expect(/[:\\/]/.test(result)).toBeFalse() + }) }) diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index 91c504742a..bee2f3e7d1 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -204,7 +204,7 @@ function normalize(defaults: unknown, raw: string, migrate?: (value: unknown) => } function workspaceStorage(dir: string) { - const head = dir.slice(0, 12) || "workspace" + const head = (dir.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-") const sum = checksum(dir) ?? "0" return `opencode.workspace.${head}.${sum}.dat` } @@ -300,6 +300,7 @@ export const PersistTesting = { localStorageDirect, localStorageWithPrefix, normalize, + workspaceStorage, } export const Persist = { From c71d1bde5e8dcc8be49c15697ad2e5d0f2607e5e Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 07:36:39 -0500 Subject: [PATCH 008/200] revert(app): "STUPID SEXY TIMELINE (#16420)" (#16745) --- bun.lock | 7 - packages/app/e2e/actions.ts | 7 +- packages/app/e2e/selectors.ts | 2 - packages/app/e2e/session/session.spec.ts | 12 +- packages/app/src/pages/session.tsx | 268 ++- .../composer/session-composer-region.tsx | 2 +- packages/app/src/pages/session/file-tabs.tsx | 4 +- .../src/pages/session/history-window.test.ts | 35 - .../app/src/pages/session/history-window.ts | 273 --- .../src/pages/session/message-timeline.tsx | 506 +++-- .../pages/session/session-timeline-header.tsx | 522 ----- .../pages/session/use-session-hash-scroll.ts | 52 +- packages/ui/package.json | 3 - .../ui/src/components/animated-number.css | 11 +- .../ui/src/components/animated-number.tsx | 29 +- packages/ui/src/components/basic-tool.css | 60 +- packages/ui/src/components/basic-tool.tsx | 356 +--- packages/ui/src/components/collapsible.css | 55 +- .../src/components/context-tool-results.tsx | 197 -- packages/ui/src/components/grow-box.tsx | 432 ---- packages/ui/src/components/message-part.css | 539 +---- packages/ui/src/components/message-part.tsx | 1865 +++++++++-------- packages/ui/src/components/motion-spring.tsx | 32 +- packages/ui/src/components/motion.tsx | 77 - .../ui/src/components/rolling-results.css | 92 - .../ui/src/components/rolling-results.tsx | 325 --- packages/ui/src/components/scroll-view.css | 19 +- packages/ui/src/components/scroll-view.tsx | 70 +- packages/ui/src/components/session-turn.css | 127 +- packages/ui/src/components/session-turn.tsx | 554 ++--- .../src/components/shell-rolling-results.tsx | 291 --- .../ui/src/components/shell-submessage.css | 12 +- packages/ui/src/components/text-reveal.css | 61 +- packages/ui/src/components/text-reveal.tsx | 115 +- packages/ui/src/components/text-shimmer.css | 17 +- packages/ui/src/components/text-shimmer.tsx | 12 - packages/ui/src/components/text-utils.ts | 17 - .../ui/src/components/tool-count-label.css | 6 +- .../ui/src/components/tool-count-label.tsx | 21 +- .../ui/src/components/tool-count-summary.css | 21 +- .../ui/src/components/tool-status-title.css | 7 +- .../ui/src/components/tool-status-title.tsx | 67 +- packages/ui/src/components/tool-utils.ts | 336 --- packages/ui/src/hooks/create-auto-scroll.tsx | 251 +-- packages/ui/src/hooks/index.ts | 1 - packages/ui/src/hooks/use-reduced-motion.ts | 10 - packages/ui/src/styles/index.css | 1 - packages/util/src/array.ts | 7 - 48 files changed, 2287 insertions(+), 5499 deletions(-) delete mode 100644 packages/app/src/pages/session/history-window.test.ts delete mode 100644 packages/app/src/pages/session/history-window.ts delete mode 100644 packages/app/src/pages/session/session-timeline-header.tsx delete mode 100644 packages/ui/src/components/context-tool-results.tsx delete mode 100644 packages/ui/src/components/grow-box.tsx delete mode 100644 packages/ui/src/components/motion.tsx delete mode 100644 packages/ui/src/components/rolling-results.css delete mode 100644 packages/ui/src/components/rolling-results.tsx delete mode 100644 packages/ui/src/components/shell-rolling-results.tsx delete mode 100644 packages/ui/src/components/text-utils.ts delete mode 100644 packages/ui/src/components/tool-utils.ts delete mode 100644 packages/ui/src/hooks/use-reduced-motion.ts diff --git a/bun.lock b/bun.lock index 5202b70d98..17ea5b347c 100644 --- a/bun.lock +++ b/bun.lock @@ -483,11 +483,8 @@ "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", "@solid-primitives/bounds": "0.1.3", - "@solid-primitives/lifecycle": "0.1.2", "@solid-primitives/media": "2.3.3", - "@solid-primitives/page-visibility": "2.1.1", "@solid-primitives/resize-observer": "2.1.3", - "@solid-primitives/rootless": "1.5.2", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "dompurify": "3.3.1", @@ -1837,14 +1834,10 @@ "@solid-primitives/keyed": ["@solid-primitives/keyed@1.5.3", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA=="], - "@solid-primitives/lifecycle": ["@solid-primitives/lifecycle@0.1.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-+K0T10kZXqorocFj0coIqt8NYm2UqoZfpF3nm2RwrDMZMV+C+SC0Oi3N6Dnq2i7W/n1cHAnfpoV4CBLsW21lJw=="], - "@solid-primitives/map": ["@solid-primitives/map@0.4.13", "", { "dependencies": { "@solid-primitives/trigger": "^1.1.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew=="], "@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="], - "@solid-primitives/page-visibility": ["@solid-primitives/page-visibility@2.1.1", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.1", "@solid-primitives/rootless": "^1.5.1", "@solid-primitives/utils": "^6.3.1" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-CV9BqMqhunf4OOyBkhJCH9f5ivg0ADavdcaBsrqoFvwIk1FoD/blPSHYM4CK8IjS/AEXNcsjlNVc34lMu+2Wdg=="], - "@solid-primitives/props": ["@solid-primitives/props@3.2.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-lZOTwFJajBrshSyg14nBMEP0h8MXzPowGO0s3OeiR3z6nXHTfj0FhzDtJMv+VYoRJKQHG2QRnJTgCzK6erARAw=="], "@solid-primitives/refs": ["@solid-primitives/refs@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg=="], diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts index 90a449d500..86147dc65d 100644 --- a/packages/app/e2e/actions.ts +++ b/packages/app/e2e/actions.ts @@ -7,7 +7,6 @@ import { createSdk, modKey, resolveDirectory, serverUrl } from "./utils" import { dropdownMenuTriggerSelector, dropdownMenuContentSelector, - sessionTimelineHeaderSelector, projectMenuTriggerSelector, projectCloseMenuSelector, projectWorkspacesToggleSelector, @@ -244,9 +243,7 @@ export async function openSessionMoreMenu(page: Page, sessionID: string) { const scroller = page.locator(".scroll-view__viewport").first() await expect(scroller).toBeVisible() - const header = page.locator(sessionTimelineHeaderSelector).first() - await expect(header).toBeVisible({ timeout: 30_000 }) - await expect(header.getByRole("heading", { level: 1 }).first()).toBeVisible({ timeout: 30_000 }) + await expect(scroller.getByRole("heading", { level: 1 }).first()).toBeVisible({ timeout: 30_000 }) const menu = page .locator(dropdownMenuContentSelector) @@ -262,7 +259,7 @@ export async function openSessionMoreMenu(page: Page, sessionID: string) { if (opened) return menu - const menuTrigger = header.getByRole("button", { name: /more options/i }).first() + const menuTrigger = scroller.getByRole("button", { name: /more options/i }).first() await expect(menuTrigger).toBeVisible() await menuTrigger.click() diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 002ac2114c..2061a11284 100644 --- a/packages/app/e2e/selectors.ts +++ b/packages/app/e2e/selectors.ts @@ -51,8 +51,6 @@ export const dropdownMenuContentSelector = '[data-component="dropdown-menu-conte export const inlineInputSelector = '[data-component="inline-input"]' -export const sessionTimelineHeaderSelector = "[data-session-title]" - export const sessionItemSelector = (sessionID: string) => `${sidebarNavSelector} [data-session-id="${sessionID}"]` export const workspaceItemSelector = (slug: string) => diff --git a/packages/app/e2e/session/session.spec.ts b/packages/app/e2e/session/session.spec.ts index e541738c59..68d9929499 100644 --- a/packages/app/e2e/session/session.spec.ts +++ b/packages/app/e2e/session/session.spec.ts @@ -7,7 +7,7 @@ import { openSharePopover, withSession, } from "../actions" -import { sessionItemSelector, inlineInputSelector, sessionTimelineHeaderSelector } from "../selectors" +import { sessionItemSelector, inlineInputSelector } from "../selectors" const shareDisabled = process.env.OPENCODE_DISABLE_SHARE === "true" || process.env.OPENCODE_DISABLE_SHARE === "1" @@ -39,14 +39,12 @@ test("session can be renamed via header menu", async ({ page, sdk, gotoSession } await withSession(sdk, originalTitle, async (session) => { await seedMessage(sdk, session.id) await gotoSession(session.id) - await expect(page.locator(sessionTimelineHeaderSelector).getByRole("heading", { level: 1 }).first()).toHaveText( - originalTitle, - ) + await expect(page.getByRole("heading", { level: 1 }).first()).toHaveText(originalTitle) const menu = await openSessionMoreMenu(page, session.id) await clickMenuItem(menu, /rename/i) - const input = page.locator(sessionTimelineHeaderSelector).locator(inlineInputSelector).first() + const input = page.locator(".scroll-view__viewport").locator(inlineInputSelector).first() await expect(input).toBeVisible() await expect(input).toBeFocused() await input.fill(renamedTitle) @@ -63,9 +61,7 @@ test("session can be renamed via header menu", async ({ page, sdk, gotoSession } ) .toBe(renamedTitle) - await expect(page.locator(sessionTimelineHeaderSelector).getByRole("heading", { level: 1 }).first()).toHaveText( - renamedTitle, - ) + await expect(page.getByRole("heading", { level: 1 }).first()).toHaveText(renamedTitle) }) }) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 90769a28ac..3f5da0c947 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -41,12 +41,220 @@ import { createScrollSpy } from "@/pages/session/scroll-spy" import { SessionMobileTabs } from "@/pages/session/session-mobile-tabs" import { SessionSidePanel } from "@/pages/session/session-side-panel" import { TerminalPanel } from "@/pages/session/terminal-panel" -import { createSessionHistoryWindow, emptyUserMessages } from "@/pages/session/history-window" import { useSessionCommands } from "@/pages/session/use-session-commands" import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll" import { same } from "@/utils/same" import { formatServerError } from "@/utils/server-errors" +const emptyUserMessages: UserMessage[] = [] + +type SessionHistoryWindowInput = { + sessionID: () => string | undefined + messagesReady: () => boolean + visibleUserMessages: () => UserMessage[] + historyMore: () => boolean + historyLoading: () => boolean + loadMore: (sessionID: string) => Promise + userScrolled: () => boolean + scroller: () => HTMLDivElement | undefined +} + +/** + * Maintains the rendered history window for a session timeline. + * + * It keeps initial paint bounded to recent turns, reveals cached turns in + * small batches while scrolling upward, and prefetches older history near top. + */ +function createSessionHistoryWindow(input: SessionHistoryWindowInput) { + const turnInit = 10 + const turnBatch = 8 + const turnScrollThreshold = 200 + const turnPrefetchBuffer = 16 + const prefetchCooldownMs = 400 + const prefetchNoGrowthLimit = 2 + + const [state, setState] = createStore({ + turnID: undefined as string | undefined, + turnStart: 0, + prefetchUntil: 0, + prefetchNoGrowth: 0, + }) + + const initialTurnStart = (len: number) => (len > turnInit ? len - turnInit : 0) + + const turnStart = createMemo(() => { + const id = input.sessionID() + const len = input.visibleUserMessages().length + if (!id || len <= 0) return 0 + if (state.turnID !== id) return initialTurnStart(len) + if (state.turnStart <= 0) return 0 + if (state.turnStart >= len) return initialTurnStart(len) + return state.turnStart + }) + + const setTurnStart = (start: number) => { + const id = input.sessionID() + const next = start > 0 ? start : 0 + if (!id) { + setState({ turnID: undefined, turnStart: next }) + return + } + setState({ turnID: id, turnStart: next }) + } + + const renderedUserMessages = createMemo( + () => { + const msgs = input.visibleUserMessages() + const start = turnStart() + if (start <= 0) return msgs + return msgs.slice(start) + }, + emptyUserMessages, + { + equals: same, + }, + ) + + const preserveScroll = (fn: () => void) => { + const el = input.scroller() + if (!el) { + fn() + return + } + const beforeTop = el.scrollTop + const beforeHeight = el.scrollHeight + fn() + requestAnimationFrame(() => { + const delta = el.scrollHeight - beforeHeight + if (!delta) return + el.scrollTop = beforeTop + delta + }) + } + + const backfillTurns = () => { + const start = turnStart() + if (start <= 0) return + + const next = start - turnBatch + const nextStart = next > 0 ? next : 0 + + preserveScroll(() => setTurnStart(nextStart)) + } + + /** Button path: reveal all cached turns, fetch older history, reveal one batch. */ + const loadAndReveal = async () => { + const id = input.sessionID() + if (!id) return + + const start = turnStart() + const beforeVisible = input.visibleUserMessages().length + + if (start > 0) setTurnStart(0) + + if (!input.historyMore() || input.historyLoading()) return + + await input.loadMore(id) + if (input.sessionID() !== id) return + + const afterVisible = input.visibleUserMessages().length + const growth = afterVisible - beforeVisible + if (state.prefetchNoGrowth) setState("prefetchNoGrowth", 0) + if (growth <= 0) return + if (turnStart() !== 0) return + + const target = Math.min(afterVisible, Math.max(beforeVisible, renderedUserMessages().length) + turnBatch) + const nextStart = Math.max(0, afterVisible - target) + preserveScroll(() => setTurnStart(nextStart)) + } + + /** Scroll/prefetch path: fetch older history from server. */ + const fetchOlderMessages = async (opts?: { prefetch?: boolean }) => { + const id = input.sessionID() + if (!id) return + if (!input.historyMore() || input.historyLoading()) return + + if (opts?.prefetch) { + const now = Date.now() + if (state.prefetchUntil > now) return + if (state.prefetchNoGrowth >= prefetchNoGrowthLimit) return + setState("prefetchUntil", now + prefetchCooldownMs) + } + + const start = turnStart() + const beforeVisible = input.visibleUserMessages().length + const beforeRendered = start <= 0 ? beforeVisible : renderedUserMessages().length + + await input.loadMore(id) + if (input.sessionID() !== id) return + + const afterVisible = input.visibleUserMessages().length + const growth = afterVisible - beforeVisible + + if (opts?.prefetch) { + setState("prefetchNoGrowth", growth > 0 ? 0 : state.prefetchNoGrowth + 1) + } else if (growth > 0 && state.prefetchNoGrowth) { + setState("prefetchNoGrowth", 0) + } + + if (growth <= 0) return + if (turnStart() !== start) return + + const reveal = !opts?.prefetch + const currentRendered = renderedUserMessages().length + const base = Math.max(beforeRendered, currentRendered) + const target = reveal ? Math.min(afterVisible, base + turnBatch) : base + const nextStart = Math.max(0, afterVisible - target) + preserveScroll(() => setTurnStart(nextStart)) + } + + const onScrollerScroll = () => { + if (!input.userScrolled()) return + const el = input.scroller() + if (!el) return + if (el.scrollTop >= turnScrollThreshold) return + + const start = turnStart() + if (start > 0) { + if (start <= turnPrefetchBuffer) { + void fetchOlderMessages({ prefetch: true }) + } + backfillTurns() + return + } + + void fetchOlderMessages() + } + + createEffect( + on( + input.sessionID, + () => { + setState({ prefetchUntil: 0, prefetchNoGrowth: 0 }) + }, + { defer: true }, + ), + ) + + createEffect( + on( + () => [input.sessionID(), input.messagesReady()] as const, + ([id, ready]) => { + if (!id || !ready) return + setTurnStart(initialTurnStart(input.visibleUserMessages().length)) + }, + { defer: true }, + ), + ) + + return { + turnStart, + setTurnStart, + renderedUserMessages, + loadAndReveal, + onScrollerScroll, + } +} + export default function Page() { const globalSync = useGlobalSync() const layout = useLayout() @@ -886,7 +1094,6 @@ export default function Page() { let scrollStateFrame: number | undefined let scrollStateTarget: HTMLDivElement | undefined - let historyFillFrame: number | undefined const scrollSpy = createScrollSpy({ onActive: (id) => { if (id === store.messageId) return @@ -897,7 +1104,7 @@ export default function Page() { const updateScrollState = (el: HTMLDivElement) => { const max = el.scrollHeight - el.clientHeight const overflow = max > 1 - const bottom = !overflow || Math.abs(el.scrollTop) <= 2 || !autoScroll.userScrolled() + const bottom = !overflow || el.scrollTop >= max - 2 if (ui.scroll.overflow === overflow && ui.scroll.bottom === bottom) return setUi("scroll", { overflow, bottom }) @@ -920,7 +1127,7 @@ export default function Page() { const resumeScroll = () => { setStore("messageId", undefined) - autoScroll.smoothScrollToBottom() + autoScroll.forceScrollToBottom() clearMessageHash() const el = scroller @@ -956,9 +1163,7 @@ export default function Page() { scroller = el autoScroll.scrollRef(el) scrollSpy.setContainer(el) - if (!el) return - scheduleScrollState(el) - scheduleHistoryFill() + if (el) scheduleScrollState(el) } createResizeObserver( @@ -967,7 +1172,6 @@ export default function Page() { const el = scroller if (el) scheduleScrollState(el) scrollSpy.markDirty() - scheduleHistoryFill() }, ) @@ -982,45 +1186,6 @@ export default function Page() { scroller: () => scroller, }) - const scheduleHistoryFill = () => { - if (historyFillFrame !== undefined) return - - historyFillFrame = requestAnimationFrame(() => { - historyFillFrame = undefined - - if (!params.id || !messagesReady()) return - if (autoScroll.userScrolled() || historyLoading()) return - - const el = scroller - if (!el) return - if (el.scrollHeight > el.clientHeight + 1) return - if (historyWindow.turnStart() <= 0 && !historyMore()) return - - void historyWindow.loadAndReveal() - }) - } - - createEffect( - on( - () => - [ - params.id, - messagesReady(), - historyWindow.turnStart(), - historyMore(), - historyLoading(), - autoScroll.userScrolled(), - visibleUserMessages().length, - ] as const, - ([id, ready, start, more, loading, scrolled]) => { - if (!id || !ready || loading || scrolled) return - if (start <= 0 && !more) return - scheduleHistoryFill() - }, - { defer: true }, - ), - ) - createResizeObserver( () => promptDock, ({ height }) => { @@ -1030,15 +1195,16 @@ export default function Page() { const el = scroller const delta = next - dockHeight - const stick = el ? Math.abs(el.scrollTop) < 10 + Math.max(0, delta) : false + const stick = el + ? !autoScroll.userScrolled() || el.scrollHeight - el.clientHeight - el.scrollTop < 10 + Math.max(0, delta) + : false dockHeight = next - if (stick) autoScroll.smoothScrollToBottom() + if (stick) autoScroll.forceScrollToBottom() if (el) scheduleScrollState(el) scrollSpy.markDirty() - scheduleHistoryFill() }, ) @@ -1068,7 +1234,6 @@ export default function Page() { document.removeEventListener("keydown", handleKeyDown) scrollSpy.destroy() if (scrollStateFrame !== undefined) cancelAnimationFrame(scrollStateFrame) - if (historyFillFrame !== undefined) cancelAnimationFrame(historyFillFrame) }) return ( @@ -1122,7 +1287,6 @@ export default function Page() { onScrollSpyScroll={scrollSpy.onScroll} onTurnBackfillScroll={historyWindow.onScrollerScroll} onAutoScrollInteraction={autoScroll.handleInteraction} - onPreserveScrollAnchor={autoScroll.preserve} centered={centered()} setContentRef={(el) => { content = el diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx index 18a02993b6..93ea3d465c 100644 --- a/packages/app/src/pages/session/composer/session-composer-region.tsx +++ b/packages/app/src/pages/session/composer/session-composer-region.tsx @@ -140,7 +140,7 @@ export function SessionComposerRegion(props: {
diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx index 07df4305f0..77643789d0 100644 --- a/packages/app/src/pages/session/file-tabs.tsx +++ b/packages/app/src/pages/session/file-tabs.tsx @@ -446,9 +446,9 @@ export function FileTabContent(props: { tab: string }) { ) return ( - + { scroll = el restoreScroll() diff --git a/packages/app/src/pages/session/history-window.test.ts b/packages/app/src/pages/session/history-window.test.ts deleted file mode 100644 index 4a9b894e27..0000000000 --- a/packages/app/src/pages/session/history-window.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { historyLoadMode, historyRevealTop } from "./history-window" - -describe("historyLoadMode", () => { - test("reveals cached turns before fetching", () => { - expect(historyLoadMode({ start: 10, more: true, loading: false })).toBe("reveal") - }) - - test("fetches older history when cache is already revealed", () => { - expect(historyLoadMode({ start: 0, more: true, loading: false })).toBe("fetch") - }) - - test("does nothing while history is unavailable or loading", () => { - expect(historyLoadMode({ start: 0, more: false, loading: false })).toBe("noop") - expect(historyLoadMode({ start: 0, more: true, loading: true })).toBe("noop") - }) -}) - -describe("historyRevealTop", () => { - test("pins the viewport to the top when older turns were revealed there", () => { - expect(historyRevealTop({ top: -400, height: 1000, gap: 0, max: 400 }, { clientHeight: 600, height: 2000 })).toBe( - -1400, - ) - }) - - test("keeps the latest turns pinned when the viewport was underfilled", () => { - expect(historyRevealTop({ top: 0, height: 200, gap: -400, max: -400 }, { clientHeight: 600, height: 2000 })).toBe(0) - }) - - test("keeps the current anchor when the user was not at the top", () => { - expect(historyRevealTop({ top: -200, height: 1000, gap: 200, max: 400 }, { clientHeight: 600, height: 2000 })).toBe( - -200, - ) - }) -}) diff --git a/packages/app/src/pages/session/history-window.ts b/packages/app/src/pages/session/history-window.ts deleted file mode 100644 index e3ef20f13d..0000000000 --- a/packages/app/src/pages/session/history-window.ts +++ /dev/null @@ -1,273 +0,0 @@ -import type { UserMessage } from "@opencode-ai/sdk/v2" -import { createEffect, createMemo, on } from "solid-js" -import { createStore } from "solid-js/store" -import { same } from "@/utils/same" - -export const emptyUserMessages: UserMessage[] = [] - -export type SessionHistoryWindowInput = { - sessionID: () => string | undefined - messagesReady: () => boolean - visibleUserMessages: () => UserMessage[] - historyMore: () => boolean - historyLoading: () => boolean - loadMore: (sessionID: string) => Promise - userScrolled: () => boolean - scroller: () => HTMLDivElement | undefined -} - -type Snap = { - top: number - height: number - gap: number - max: number -} - -export const historyLoadMode = (input: { start: number; more: boolean; loading: boolean }) => { - if (input.start > 0) return "reveal" - if (!input.more || input.loading) return "noop" - return "fetch" -} - -export const historyRevealTop = ( - mark: { top: number; height: number; gap: number; max: number }, - next: { clientHeight: number; height: number }, - threshold = 16, -) => { - const delta = next.height - mark.height - if (delta <= 0) return mark.top - if (mark.max <= 0) return mark.top - if (mark.gap > threshold) return mark.top - - const max = next.height - next.clientHeight - if (max <= 0) return 0 - return Math.max(-max, Math.min(0, mark.top - delta)) -} - -const snap = (el: HTMLDivElement | undefined): Snap | undefined => { - if (!el) return - const max = el.scrollHeight - el.clientHeight - return { - top: el.scrollTop, - height: el.scrollHeight, - gap: max + el.scrollTop, - max, - } -} - -const clamp = (el: HTMLDivElement, top: number) => { - const max = el.scrollHeight - el.clientHeight - if (max <= 0) return 0 - return Math.max(-max, Math.min(0, top)) -} - -const revealThreshold = 16 - -const reveal = (input: SessionHistoryWindowInput, mark: Snap | undefined) => { - const el = input.scroller() - if (!el || !mark) return - el.scrollTop = clamp( - el, - historyRevealTop(mark, { clientHeight: el.clientHeight, height: el.scrollHeight }, revealThreshold), - ) -} - -const preserve = (input: SessionHistoryWindowInput, fn: () => void) => { - const el = input.scroller() - if (!el) { - fn() - return - } - const top = el.scrollTop - fn() - el.scrollTop = top -} - -/** - * Maintains the rendered history window for a session timeline. - * - * It keeps initial paint bounded to recent turns, reveals cached turns in - * small batches while scrolling upward, and prefetches older history near top. - */ -export function createSessionHistoryWindow(input: SessionHistoryWindowInput) { - const turnInit = 10 - const turnBatch = 8 - const turnScrollThreshold = 200 - const turnPrefetchBuffer = 16 - const prefetchCooldownMs = 400 - const prefetchNoGrowthLimit = 2 - - const [state, setState] = createStore({ - turnID: undefined as string | undefined, - turnStart: 0, - prefetchUntil: 0, - prefetchNoGrowth: 0, - }) - - const initialTurnStart = (len: number) => (len > turnInit ? len - turnInit : 0) - - const turnStart = createMemo(() => { - const id = input.sessionID() - const len = input.visibleUserMessages().length - if (!id || len <= 0) return 0 - if (state.turnID !== id) return initialTurnStart(len) - if (state.turnStart <= 0) return 0 - if (state.turnStart >= len) return initialTurnStart(len) - return state.turnStart - }) - - const setTurnStart = (start: number) => { - const id = input.sessionID() - const next = start > 0 ? start : 0 - if (!id) { - setState({ turnID: undefined, turnStart: next }) - return - } - setState({ turnID: id, turnStart: next }) - } - - const renderedUserMessages = createMemo( - () => { - const msgs = input.visibleUserMessages() - const start = turnStart() - if (start <= 0) return msgs - return msgs.slice(start) - }, - emptyUserMessages, - { - equals: same, - }, - ) - - const backfillTurns = () => { - const start = turnStart() - if (start <= 0) return - - const next = start - turnBatch - const nextStart = next > 0 ? next : 0 - - preserve(input, () => setTurnStart(nextStart)) - } - - /** Button path: reveal cached turns first, then fetch older history. */ - const loadAndReveal = async () => { - const id = input.sessionID() - if (!id) return - - const start = turnStart() - const mode = historyLoadMode({ - start, - more: input.historyMore(), - loading: input.historyLoading(), - }) - - if (mode === "reveal") { - const mark = snap(input.scroller()) - setTurnStart(0) - reveal(input, mark) - return - } - - if (mode === "noop") return - - const beforeVisible = input.visibleUserMessages().length - const mark = snap(input.scroller()) - - await input.loadMore(id) - if (input.sessionID() !== id) return - - const afterVisible = input.visibleUserMessages().length - const growth = afterVisible - beforeVisible - if (growth <= 0) return - if (state.prefetchNoGrowth) setState("prefetchNoGrowth", 0) - - reveal(input, mark) - } - - /** Scroll/prefetch path: fetch older history from server. */ - const fetchOlderMessages = async (opts?: { prefetch?: boolean }) => { - const id = input.sessionID() - if (!id) return - if (!input.historyMore() || input.historyLoading()) return - - if (opts?.prefetch) { - const now = Date.now() - if (state.prefetchUntil > now) return - if (state.prefetchNoGrowth >= prefetchNoGrowthLimit) return - setState("prefetchUntil", now + prefetchCooldownMs) - } - - const start = turnStart() - const beforeVisible = input.visibleUserMessages().length - const beforeRendered = start <= 0 ? beforeVisible : renderedUserMessages().length - - await input.loadMore(id) - if (input.sessionID() !== id) return - - const afterVisible = input.visibleUserMessages().length - const growth = afterVisible - beforeVisible - - if (opts?.prefetch) { - setState("prefetchNoGrowth", growth > 0 ? 0 : state.prefetchNoGrowth + 1) - } else if (growth > 0 && state.prefetchNoGrowth) { - setState("prefetchNoGrowth", 0) - } - - if (growth <= 0) return - if (turnStart() !== start) return - - const revealMore = !opts?.prefetch - const currentRendered = renderedUserMessages().length - const base = Math.max(beforeRendered, currentRendered) - const target = revealMore ? Math.min(afterVisible, base + turnBatch) : base - const nextStart = Math.max(0, afterVisible - target) - preserve(input, () => setTurnStart(nextStart)) - } - - const onScrollerScroll = () => { - if (!input.userScrolled()) return - const el = input.scroller() - if (!el) return - if (el.scrollHeight - el.clientHeight + el.scrollTop >= turnScrollThreshold) return - - const start = turnStart() - if (start > 0) { - if (start <= turnPrefetchBuffer) { - void fetchOlderMessages({ prefetch: true }) - } - backfillTurns() - return - } - - void fetchOlderMessages() - } - - createEffect( - on( - input.sessionID, - () => { - setState({ prefetchUntil: 0, prefetchNoGrowth: 0 }) - }, - { defer: true }, - ), - ) - - createEffect( - on( - () => [input.sessionID(), input.messagesReady()] as const, - ([id, ready]) => { - if (!id || !ready) return - setTurnStart(initialTurnStart(input.visibleUserMessages().length)) - }, - { defer: true }, - ), - ) - - return { - turnStart, - setTurnStart, - renderedUserMessages, - loadAndReveal, - onScrollerScroll, - } -} diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index e93ca11a36..ce6a01378c 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -1,31 +1,27 @@ -import { - For, - Index, - createEffect, - createMemo, - createSignal, - on, - onCleanup, - Show, - startTransition, - type JSX, -} from "solid-js" -import { createStore } from "solid-js/store" -import { useParams } from "@solidjs/router" +import { For, createEffect, createMemo, on, onCleanup, Show, Index, type JSX } from "solid-js" +import { createStore, produce } from "solid-js/store" +import { useNavigate, useParams } from "@solidjs/router" import { Button } from "@opencode-ai/ui/button" import { FileIcon } from "@opencode-ai/ui/file-icon" import { Icon } from "@opencode-ai/ui/icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" +import { Dialog } from "@opencode-ai/ui/dialog" +import { InlineInput } from "@opencode-ai/ui/inline-input" import { SessionTurn } from "@opencode-ai/ui/session-turn" import { ScrollView } from "@opencode-ai/ui/scroll-view" import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2" +import { showToast } from "@opencode-ai/ui/toast" import { Binary } from "@opencode-ai/util/binary" import { getFilename } from "@opencode-ai/util/path" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" +import { SessionContextUsage } from "@/components/session-context-usage" +import { useDialog } from "@opencode-ai/ui/context/dialog" import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" +import { useSDK } from "@/context/sdk" import { useSync } from "@/context/sync" import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note" -import { SessionTimelineHeader } from "@/pages/session/session-timeline-header" type MessageComment = { path: string @@ -37,9 +33,7 @@ type MessageComment = { } const emptyMessages: MessageType[] = [] - -const isDefaultSessionTitle = (title?: string) => - !!title && /^(New session - |Child session - )\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(title) +const idle = { type: "idle" as const } const messageComments = (parts: Part[]): MessageComment[] => parts.flatMap((part) => { @@ -116,8 +110,6 @@ function createTimelineStaging(input: TimelineStageInput) { completedSession: "", count: 0, }) - const [readySession, setReadySession] = createSignal("") - let active = "" const stagedCount = createMemo(() => { const total = input.messages().length @@ -142,46 +134,23 @@ function createTimelineStaging(input: TimelineStageInput) { cancelAnimationFrame(frame) frame = undefined } - const scheduleReady = (sessionKey: string) => { - if (input.sessionKey() !== sessionKey) return - if (readySession() === sessionKey) return - setReadySession(sessionKey) - } createEffect( on( () => [input.sessionKey(), input.turnStart() > 0, input.messages().length] as const, ([sessionKey, isWindowed, total]) => { - const switched = active !== sessionKey - if (switched) { - active = sessionKey - setReadySession("") - } - - const staging = state.activeSession === sessionKey && state.completedSession !== sessionKey - const shouldStage = isWindowed && total > input.config.init && state.completedSession !== sessionKey - - if (staging && !switched && shouldStage && frame !== undefined) return - cancel() - - if (shouldStage) setReadySession("") + const shouldStage = + isWindowed && + total > input.config.init && + state.completedSession !== sessionKey && + state.activeSession !== sessionKey if (!shouldStage) { - setState({ - activeSession: "", - completedSession: isWindowed ? sessionKey : state.completedSession, - count: total, - }) - if (total <= 0) { - setReadySession("") - return - } - if (readySession() !== sessionKey) scheduleReady(sessionKey) + setState({ activeSession: "", count: total }) return } let count = Math.min(total, input.config.init) - if (staging) count = Math.min(total, Math.max(count, state.count)) setState({ activeSession: sessionKey, count }) const step = () => { @@ -191,11 +160,10 @@ function createTimelineStaging(input: TimelineStageInput) { } const currentTotal = input.messages().length count = Math.min(currentTotal, count + input.config.batch) - startTransition(() => setState("count", count)) + setState("count", count) if (count >= currentTotal) { setState({ completedSession: sessionKey, activeSession: "" }) frame = undefined - scheduleReady(sessionKey) return } frame = requestAnimationFrame(step) @@ -209,12 +177,9 @@ function createTimelineStaging(input: TimelineStageInput) { const key = input.sessionKey() return state.activeSession === key && state.completedSession !== key }) - const ready = createMemo(() => readySession() === input.sessionKey()) - onCleanup(() => { - cancel() - }) - return { messages: stagedUserMessages, isStaging, ready } + onCleanup(cancel) + return { messages: stagedUserMessages, isStaging } } export function MessageTimeline(props: { @@ -231,7 +196,6 @@ export function MessageTimeline(props: { onScrollSpyScroll: () => void onTurnBackfillScroll: () => void onAutoScrollInteraction: (event: MouseEvent) => void - onPreserveScrollAnchor: (target: HTMLElement) => void centered: boolean setContentRef: (el: HTMLDivElement) => void turnStart: number @@ -246,19 +210,14 @@ export function MessageTimeline(props: { let touchGesture: number | undefined const params = useParams() + const navigate = useNavigate() + const sdk = useSDK() const sync = useSync() const settings = useSettings() + const dialog = useDialog() const language = useLanguage() - const trigger = (target: EventTarget | null) => { - const next = - target instanceof Element - ? target.closest('[data-slot="collapsible-trigger"], [data-slot="accordion-trigger"], [data-scroll-preserve]') - : undefined - if (!(next instanceof HTMLElement)) return - return next - } - + const rendered = createMemo(() => props.renderedUserMessages.map((message) => message.id)) const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) const sessionID = createMemo(() => params.id) const sessionMessages = createMemo(() => { @@ -271,20 +230,28 @@ export function MessageTimeline(props: { (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number", ), ) - const sessionStatus = createMemo(() => sync.data.session_status[sessionID() ?? ""]?.type ?? "idle") + const sessionStatus = createMemo(() => { + const id = sessionID() + if (!id) return idle + return sync.data.session_status[id] ?? idle + }) const activeMessageID = createMemo(() => { - const messages = sessionMessages() - const message = pending() - if (message?.parentID) { - const result = Binary.search(messages, message.parentID, (item) => item.id) - const parent = result.found ? messages[result.index] : messages.find((item) => item.id === message.parentID) - if (parent?.role === "user") return parent.id + const parentID = pending()?.parentID + if (parentID) { + const messages = sessionMessages() + const result = Binary.search(messages, parentID, (message) => message.id) + const message = result.found ? messages[result.index] : messages.find((item) => item.id === parentID) + if (message && message.role === "user") return message.id } - if (sessionStatus() === "idle") return undefined - for (let i = messages.length - 1; i >= 0; i--) { - if (messages[i].role === "user") return messages[i].id + const status = sessionStatus() + if (status.type !== "idle") { + const messages = sessionMessages() + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === "user") return messages[i].id + } } + return undefined }) const info = createMemo(() => { @@ -292,19 +259,9 @@ export function MessageTimeline(props: { if (!id) return return sync.session.get(id) }) - const titleValue = createMemo(() => { - const title = info()?.title - if (!title) return - if (isDefaultSessionTitle(title)) return language.t("command.session.new") - return title - }) - const defaultTitle = createMemo(() => isDefaultSessionTitle(info()?.title)) - const headerTitle = createMemo( - () => titleValue() ?? (props.renderedUserMessages.length ? language.t("command.session.new") : undefined), - ) - const placeholderTitle = createMemo(() => defaultTitle() || (!info()?.title && props.renderedUserMessages.length > 0)) + const titleValue = createMemo(() => info()?.title) const parentID = createMemo(() => info()?.parentID) - const showHeader = createMemo(() => !!(headerTitle() || parentID())) + const showHeader = createMemo(() => !!(titleValue() || parentID())) const stageCfg = { init: 1, batch: 3 } const staging = createTimelineStaging({ sessionKey, @@ -312,7 +269,212 @@ export function MessageTimeline(props: { messages: () => props.renderedUserMessages, config: stageCfg, }) - const rendered = createMemo(() => staging.messages().map((message) => message.id)) + + const [title, setTitle] = createStore({ + draft: "", + editing: false, + saving: false, + menuOpen: false, + pendingRename: false, + }) + let titleRef: HTMLInputElement | undefined + + const errorMessage = (err: unknown) => { + if (err && typeof err === "object" && "data" in err) { + const data = (err as { data?: { message?: string } }).data + if (data?.message) return data.message + } + if (err instanceof Error) return err.message + return language.t("common.requestFailed") + } + + createEffect( + on( + sessionKey, + () => setTitle({ draft: "", editing: false, saving: false, menuOpen: false, pendingRename: false }), + { defer: true }, + ), + ) + + const openTitleEditor = () => { + if (!sessionID()) return + setTitle({ editing: true, draft: titleValue() ?? "" }) + requestAnimationFrame(() => { + titleRef?.focus() + titleRef?.select() + }) + } + + const closeTitleEditor = () => { + if (title.saving) return + setTitle({ editing: false, saving: false }) + } + + const saveTitleEditor = async () => { + const id = sessionID() + if (!id) return + if (title.saving) return + + const next = title.draft.trim() + if (!next || next === (titleValue() ?? "")) { + setTitle({ editing: false, saving: false }) + return + } + + setTitle("saving", true) + await sdk.client.session + .update({ sessionID: id, title: next }) + .then(() => { + sync.set( + produce((draft) => { + const index = draft.session.findIndex((s) => s.id === id) + if (index !== -1) draft.session[index].title = next + }), + ) + setTitle({ editing: false, saving: false }) + }) + .catch((err) => { + setTitle("saving", false) + showToast({ + title: language.t("common.requestFailed"), + description: errorMessage(err), + }) + }) + } + + const navigateAfterSessionRemoval = (sessionID: string, parentID?: string, nextSessionID?: string) => { + if (params.id !== sessionID) return + if (parentID) { + navigate(`/${params.dir}/session/${parentID}`) + return + } + if (nextSessionID) { + navigate(`/${params.dir}/session/${nextSessionID}`) + return + } + navigate(`/${params.dir}/session`) + } + + const archiveSession = async (sessionID: string) => { + const session = sync.session.get(sessionID) + if (!session) return + + const sessions = sync.data.session ?? [] + const index = sessions.findIndex((s) => s.id === sessionID) + const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1]) + + await sdk.client.session + .update({ sessionID, time: { archived: Date.now() } }) + .then(() => { + sync.set( + produce((draft) => { + const index = draft.session.findIndex((s) => s.id === sessionID) + if (index !== -1) draft.session.splice(index, 1) + }), + ) + navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id) + }) + .catch((err) => { + showToast({ + title: language.t("common.requestFailed"), + description: errorMessage(err), + }) + }) + } + + const deleteSession = async (sessionID: string) => { + const session = sync.session.get(sessionID) + if (!session) return false + + const sessions = (sync.data.session ?? []).filter((s) => !s.parentID && !s.time?.archived) + const index = sessions.findIndex((s) => s.id === sessionID) + const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1]) + + const result = await sdk.client.session + .delete({ sessionID }) + .then((x) => x.data) + .catch((err) => { + showToast({ + title: language.t("session.delete.failed.title"), + description: errorMessage(err), + }) + return false + }) + + if (!result) return false + + sync.set( + produce((draft) => { + const removed = new Set([sessionID]) + + const byParent = new Map() + for (const item of draft.session) { + const parentID = item.parentID + if (!parentID) continue + const existing = byParent.get(parentID) + if (existing) { + existing.push(item.id) + continue + } + byParent.set(parentID, [item.id]) + } + + const stack = [sessionID] + while (stack.length) { + const parentID = stack.pop() + if (!parentID) continue + + const children = byParent.get(parentID) + if (!children) continue + + for (const child of children) { + if (removed.has(child)) continue + removed.add(child) + stack.push(child) + } + } + + draft.session = draft.session.filter((s) => !removed.has(s.id)) + }), + ) + + navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id) + return true + } + + const navigateParent = () => { + const id = parentID() + if (!id) return + navigate(`/${params.dir}/session/${id}`) + } + + function DialogDeleteSession(props: { sessionID: string }) { + const name = createMemo(() => sync.session.get(props.sessionID)?.title ?? language.t("command.session.new")) + const handleDelete = async () => { + await deleteSession(props.sessionID) + dialog.close() + } + + return ( + +
+
+ + {language.t("session.delete.confirm", { name: name() })} + +
+
+ + +
+
+
+ ) + } return (
- { const root = e.currentTarget @@ -381,18 +532,9 @@ export function MessageTimeline(props: { touchGesture = undefined }} onPointerDown={(e) => { - const next = trigger(e.target) - if (next) props.onPreserveScrollAnchor(next) - if (e.target !== e.currentTarget) return props.onMarkScrollGesture(e.currentTarget) }} - onKeyDown={(e) => { - if (e.key !== "Enter" && e.key !== " ") return - const next = trigger(e.target) - if (!next) return - props.onPreserveScrollAnchor(next) - }} onScroll={(e) => { props.onScheduleScrollState(e.currentTarget) props.onTurnBackfillScroll() @@ -401,24 +543,134 @@ export function MessageTimeline(props: { props.onMarkScrollGesture(e.currentTarget) if (props.isDesktop) props.onScrollSpyScroll() }} - onClick={(e) => { - props.onAutoScrollInteraction(e) - }} + onClick={props.onAutoScrollInteraction} class="relative min-w-0 w-full h-full" style={{ - "--session-title-height": showHeader() ? "72px" : "0px", + "--session-title-height": showHeader() ? "40px" : "0px", "--sticky-accordion-top": showHeader() ? "48px" : "0px", }} > -
+
+ +
+
+
+ + + + + + {titleValue()} + + } + > + { + titleRef = el + }} + value={title.draft} + disabled={title.saving} + class="text-14-medium text-text-strong grow-1 min-w-0 pl-2 rounded-[6px]" + style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }} + onInput={(event) => setTitle("draft", event.currentTarget.value)} + onKeyDown={(event) => { + event.stopPropagation() + if (event.key === "Enter") { + event.preventDefault() + void saveTitleEditor() + return + } + if (event.key === "Escape") { + event.preventDefault() + closeTitleEditor() + } + }} + onBlur={closeTitleEditor} + /> + + +
+ + {(id) => ( +
+ + setTitle("menuOpen", open)} + > + + + { + if (!title.pendingRename) return + event.preventDefault() + setTitle("pendingRename", false) + openTitleEditor() + }} + > + { + setTitle("pendingRename", true) + setTitle("menuOpen", false) + }} + > + {language.t("common.rename")} + + void archiveSession(id())}> + {language.t("common.archive")} + + + dialog.show(() => )} + > + {language.t("common.delete")} + + + + +
+ )} +
+
+
+
+
{(messageID) => { - // Capture at creation time: animate only messages added after the - // timeline finishes its initial backfill staging, plus the first - // turn while a brand new session is still using its default title. - const isNew = - staging.ready() || - (defaultTitle() && - sessionStatus() !== "idle" && - props.renderedUserMessages.length === 1 && - messageID === props.renderedUserMessages[0]?.id) const active = createMemo(() => activeMessageID() === messageID) const queued = createMemo(() => { if (active()) return false @@ -457,10 +700,7 @@ export function MessageTimeline(props: { return false }) const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []), [], { - equals: (a, b) => { - if (a.length !== b.length) return false - return a.every((x, i) => x.path === b[i].path && x.comment === b[i].comment) - }, + equals: (a, b) => JSON.stringify(a) === JSON.stringify(b), }) const commentCount = createMemo(() => comments().length) return ( @@ -473,7 +713,7 @@ export function MessageTimeline(props: { }} classList={{ "min-w-0 w-full max-w-full": true, - "md:max-w-[500px] 2xl:max-w-[700px]": props.centered, + "md:max-w-200 2xl:max-w-[1000px]": props.centered, }} > 0}> @@ -517,7 +757,7 @@ export function MessageTimeline(props: { messageID={messageID} active={active()} queued={queued()} - animate={isNew || active()} + status={active() ? sessionStatus() : undefined} showReasoningSummaries={settings.general.showReasoningSummaries()} shellToolDefaultOpen={settings.general.shellToolPartsExpanded()} editToolDefaultOpen={settings.general.editToolPartsExpanded()} diff --git a/packages/app/src/pages/session/session-timeline-header.tsx b/packages/app/src/pages/session/session-timeline-header.tsx deleted file mode 100644 index 32412f0a7f..0000000000 --- a/packages/app/src/pages/session/session-timeline-header.tsx +++ /dev/null @@ -1,522 +0,0 @@ -import { createEffect, createMemo, on, onCleanup, Show } from "solid-js" -import { createStore, produce } from "solid-js/store" -import { useNavigate, useParams } from "@solidjs/router" -import { Button } from "@opencode-ai/ui/button" -import { useReducedMotion } from "@opencode-ai/ui/hooks" -import { IconButton } from "@opencode-ai/ui/icon-button" -import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" -import { Dialog } from "@opencode-ai/ui/dialog" -import { InlineInput } from "@opencode-ai/ui/inline-input" -import { animate, type AnimationPlaybackControls, clearFadeStyles, FAST_SPRING } from "@opencode-ai/ui/motion" -import { showToast } from "@opencode-ai/ui/toast" -import { errorMessage } from "@/pages/layout/helpers" -import { SessionContextUsage } from "@/components/session-context-usage" -import { useDialog } from "@opencode-ai/ui/context/dialog" -import { useLanguage } from "@/context/language" -import { useSDK } from "@/context/sdk" -import { useSync } from "@/context/sync" - -export function SessionTimelineHeader(props: { - centered: boolean - showHeader: () => boolean - sessionKey: () => string - sessionID: () => string | undefined - parentID: () => string | undefined - titleValue: () => string | undefined - headerTitle: () => string | undefined - placeholderTitle: () => boolean -}) { - const navigate = useNavigate() - const params = useParams() - const sdk = useSDK() - const sync = useSync() - const dialog = useDialog() - const language = useLanguage() - const reduce = useReducedMotion() - - const [title, setTitle] = createStore({ - draft: "", - editing: false, - saving: false, - menuOpen: false, - pendingRename: false, - }) - const [headerText, setHeaderText] = createStore({ - session: props.sessionKey(), - value: props.headerTitle(), - prev: undefined as string | undefined, - muted: props.placeholderTitle(), - prevMuted: false, - }) - let headerAnim: AnimationPlaybackControls | undefined - let enterAnim: AnimationPlaybackControls | undefined - let leaveAnim: AnimationPlaybackControls | undefined - let titleRef: HTMLInputElement | undefined - let headerRef: HTMLDivElement | undefined - let enterRef: HTMLSpanElement | undefined - let leaveRef: HTMLSpanElement | undefined - - const clearHeaderAnim = () => { - headerAnim?.stop() - headerAnim = undefined - } - - const animateHeader = () => { - const el = headerRef - if (!el) return - - clearHeaderAnim() - if (!headerText.muted || reduce()) { - el.style.opacity = "1" - return - } - - headerAnim = animate(el, { opacity: [0, 1] }, { type: "spring", visualDuration: 1.0, bounce: 0 }) - headerAnim.finished.then(() => { - if (headerRef !== el) return - clearFadeStyles(el) - }) - } - - const clearTitleAnims = () => { - enterAnim?.stop() - enterAnim = undefined - leaveAnim?.stop() - leaveAnim = undefined - } - - const settleTitleEnter = () => { - if (enterRef) clearFadeStyles(enterRef) - } - - const hideLeave = () => { - if (!leaveRef) return - leaveRef.style.opacity = "0" - leaveRef.style.filter = "" - leaveRef.style.transform = "" - } - - const animateEnterSpan = () => { - if (!enterRef) return - if (reduce()) { - settleTitleEnter() - return - } - enterAnim = animate( - enterRef, - { opacity: [0, 1], filter: ["blur(2px)", "blur(0px)"], transform: ["translateY(-2px)", "translateY(0)"] }, - FAST_SPRING, - ) - enterAnim.finished.then(() => settleTitleEnter()) - } - - const crossfadeTitle = (nextTitle: string, nextMuted: boolean) => { - clearTitleAnims() - setHeaderText({ prev: headerText.value, prevMuted: headerText.muted }) - setHeaderText({ value: nextTitle, muted: nextMuted }) - - if (reduce()) { - setHeaderText({ prev: undefined, prevMuted: false }) - hideLeave() - settleTitleEnter() - return - } - - if (leaveRef) { - leaveAnim = animate( - leaveRef, - { opacity: [1, 0], filter: ["blur(0px)", "blur(2px)"], transform: ["translateY(0)", "translateY(2px)"] }, - FAST_SPRING, - ) - leaveAnim.finished.then(() => { - setHeaderText({ prev: undefined, prevMuted: false }) - hideLeave() - }) - } - - animateEnterSpan() - } - - const fadeInTitle = (nextTitle: string, nextMuted: boolean) => { - clearTitleAnims() - setHeaderText({ value: nextTitle, muted: nextMuted, prev: undefined, prevMuted: false }) - animateEnterSpan() - } - - const snapTitle = (nextTitle: string | undefined, nextMuted: boolean) => { - clearTitleAnims() - setHeaderText({ value: nextTitle, muted: nextMuted, prev: undefined, prevMuted: false }) - settleTitleEnter() - } - - createEffect( - on(props.showHeader, (show, prev) => { - if (!show) { - clearHeaderAnim() - return - } - if (show === prev) return - animateHeader() - }), - ) - - createEffect( - on( - () => [props.sessionKey(), props.headerTitle(), props.placeholderTitle()] as const, - ([nextSession, nextTitle, nextMuted]) => { - if (nextSession !== headerText.session) { - setHeaderText("session", nextSession) - if (nextTitle && nextMuted) { - fadeInTitle(nextTitle, nextMuted) - return - } - snapTitle(nextTitle, nextMuted) - return - } - if (nextTitle === headerText.value && nextMuted === headerText.muted) return - if (!nextTitle) { - snapTitle(undefined, false) - return - } - if (!headerText.value) { - fadeInTitle(nextTitle, nextMuted) - return - } - if (title.saving || title.editing) { - snapTitle(nextTitle, nextMuted) - return - } - crossfadeTitle(nextTitle, nextMuted) - }, - ), - ) - - onCleanup(() => { - clearHeaderAnim() - clearTitleAnims() - }) - - const toastError = (err: unknown) => errorMessage(err, language.t("common.requestFailed")) - - createEffect( - on( - props.sessionKey, - () => setTitle({ draft: "", editing: false, saving: false, menuOpen: false, pendingRename: false }), - { defer: true }, - ), - ) - - const openTitleEditor = () => { - if (!props.sessionID()) return - setTitle({ editing: true, draft: props.titleValue() ?? "" }) - requestAnimationFrame(() => { - titleRef?.focus() - titleRef?.select() - }) - } - - const closeTitleEditor = () => { - if (title.saving) return - setTitle({ editing: false, saving: false }) - } - - const saveTitleEditor = async () => { - const id = props.sessionID() - if (!id) return - if (title.saving) return - - const next = title.draft.trim() - if (!next || next === (props.titleValue() ?? "")) { - setTitle({ editing: false, saving: false }) - return - } - - setTitle("saving", true) - await sdk.client.session - .update({ sessionID: id, title: next }) - .then(() => { - sync.set( - produce((draft) => { - const index = draft.session.findIndex((session) => session.id === id) - if (index !== -1) draft.session[index].title = next - }), - ) - setTitle({ editing: false, saving: false }) - }) - .catch((err) => { - setTitle("saving", false) - showToast({ - title: language.t("common.requestFailed"), - description: toastError(err), - }) - }) - } - - const navigateAfterSessionRemoval = (sessionID: string, parentID?: string, nextSessionID?: string) => { - if (params.id !== sessionID) return - if (parentID) { - navigate(`/${params.dir}/session/${parentID}`) - return - } - if (nextSessionID) { - navigate(`/${params.dir}/session/${nextSessionID}`) - return - } - navigate(`/${params.dir}/session`) - } - - const archiveSession = async (sessionID: string) => { - const session = sync.session.get(sessionID) - if (!session) return - - const sessions = sync.data.session ?? [] - const index = sessions.findIndex((item) => item.id === sessionID) - const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1]) - - await sdk.client.session - .update({ sessionID, time: { archived: Date.now() } }) - .then(() => { - sync.set( - produce((draft) => { - const index = draft.session.findIndex((item) => item.id === sessionID) - if (index !== -1) draft.session.splice(index, 1) - }), - ) - navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id) - }) - .catch((err) => { - showToast({ - title: language.t("common.requestFailed"), - description: toastError(err), - }) - }) - } - - const deleteSession = async (sessionID: string) => { - const session = sync.session.get(sessionID) - if (!session) return false - - const sessions = (sync.data.session ?? []).filter((item) => !item.parentID && !item.time?.archived) - const index = sessions.findIndex((item) => item.id === sessionID) - const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1]) - - const result = await sdk.client.session - .delete({ sessionID }) - .then((x) => x.data) - .catch((err) => { - showToast({ - title: language.t("session.delete.failed.title"), - description: toastError(err), - }) - return false - }) - - if (!result) return false - - sync.set( - produce((draft) => { - const removed = new Set([sessionID]) - const byParent = new Map() - - for (const item of draft.session) { - const parentID = item.parentID - if (!parentID) continue - - const existing = byParent.get(parentID) - if (existing) { - existing.push(item.id) - continue - } - byParent.set(parentID, [item.id]) - } - - const stack = [sessionID] - while (stack.length) { - const parentID = stack.pop() - if (!parentID) continue - - const children = byParent.get(parentID) - if (!children) continue - - for (const child of children) { - if (removed.has(child)) continue - removed.add(child) - stack.push(child) - } - } - - draft.session = draft.session.filter((item) => !removed.has(item.id)) - }), - ) - - navigateAfterSessionRemoval(sessionID, session.parentID, nextSession?.id) - return true - } - - const navigateParent = () => { - const id = props.parentID() - if (!id) return - navigate(`/${params.dir}/session/${id}`) - } - - function DialogDeleteSession(input: { sessionID: string }) { - const name = createMemo(() => sync.session.get(input.sessionID)?.title ?? language.t("command.session.new")) - - const handleDelete = async () => { - await deleteSession(input.sessionID) - dialog.close() - } - - return ( - -
-
- - {language.t("session.delete.confirm", { name: name() })} - -
-
- - -
-
-
- ) - } - - return ( - -
{ - headerRef = el - el.style.opacity = "0" - }} - class="pointer-events-none absolute inset-x-0 top-0 z-30" - > -
-
-
- -
- -
-
- - - - - {headerText.value} - - - {headerText.prev} - - - - } - > - { - titleRef = el - }} - value={title.draft} - disabled={title.saving} - class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px]" - style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }} - onInput={(event) => setTitle("draft", event.currentTarget.value)} - onKeyDown={(event) => { - event.stopPropagation() - if (event.key === "Enter") { - event.preventDefault() - void saveTitleEditor() - return - } - if (event.key === "Escape") { - event.preventDefault() - closeTitleEditor() - } - }} - onBlur={closeTitleEditor} - /> - - -
- - {(id) => ( -
- - setTitle("menuOpen", open)} - > - - - { - if (!title.pendingRename) return - event.preventDefault() - setTitle("pendingRename", false) - openTitleEditor() - }} - > - { - setTitle("pendingRename", true) - setTitle("menuOpen", false) - }} - > - {language.t("common.rename")} - - void archiveSession(id())}> - {language.t("common.archive")} - - - dialog.show(() => )}> - {language.t("common.delete")} - - - - -
- )} -
-
-
-
-
- ) -} 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 278a1ba6e5..20e88a3ea3 100644 --- a/packages/app/src/pages/session/use-session-hash-scroll.ts +++ b/packages/app/src/pages/session/use-session-hash-scroll.ts @@ -1,5 +1,6 @@ import type { UserMessage } from "@opencode-ai/sdk/v2" -import { createEffect, createMemo, onCleanup, onMount } from "solid-js" +import { useLocation, useNavigate } from "@solidjs/router" +import { createEffect, createMemo, onMount } from "solid-js" import { messageIdFromHash } from "./message-id-from-hash" export { messageIdFromHash } from "./message-id-from-hash" @@ -15,7 +16,7 @@ export const useSessionHashScroll = (input: { setPendingMessage: (value: string | undefined) => void setActiveMessage: (message: UserMessage | undefined) => void setTurnStart: (value: number) => void - autoScroll: { pause: () => void; snapToBottom: () => void } + autoScroll: { pause: () => void; forceScrollToBottom: () => void } scroller: () => HTMLDivElement | undefined anchor: (id: string) => string scheduleScrollState: (el: HTMLDivElement) => void @@ -26,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.pathname + window.location.search) + if (!location.hash) return + navigate(location.pathname + location.search, { replace: true }) } const updateHash = (id: string) => { - window.history.replaceState(null, "", `${window.location.pathname}${window.location.search}#${input.anchor(id)}`) + navigate(location.pathname + location.search + `#${input.anchor(id)}`, { + replace: true, + }) } const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => { @@ -41,15 +47,15 @@ export const useSessionHashScroll = (input: { const a = el.getBoundingClientRect() const b = root.getBoundingClientRect() - const title = parseFloat(getComputedStyle(root).getPropertyValue("--session-title-height")) - const inset = Number.isNaN(title) ? 0 : title - // With column-reverse, scrollTop is negative — don't clamp to 0 - const top = a.top - b.top + root.scrollTop - inset + const sticky = root.querySelector("[data-session-title]") + const inset = sticky instanceof HTMLElement ? sticky.offsetHeight : 0 + const top = Math.max(0, a.top - b.top + root.scrollTop - inset) root.scrollTo({ top, behavior }) return true } 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 @@ -97,9 +103,9 @@ 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.snapToBottom() + input.autoScroll.forceScrollToBottom() const el = input.scroller() if (el) input.scheduleScrollState(el) return @@ -123,26 +129,13 @@ export const useSessionHashScroll = (input: { return } - input.autoScroll.snapToBottom() + input.autoScroll.forceScrollToBottom() const el = input.scroller() if (el) input.scheduleScrollState(el) } - onMount(() => { - 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)) - }) - createEffect(() => { + location.hash if (!input.sessionID() || !input.messagesReady()) return requestAnimationFrame(() => applyHash("auto")) }) @@ -166,6 +159,7 @@ export const useSessionHashScroll = (input: { } } + if (!targetId) targetId = messageIdFromHash(location.hash) if (!targetId) return if (input.currentMessageId() === targetId) return @@ -177,6 +171,12 @@ export const useSessionHashScroll = (input: { requestAnimationFrame(() => scrollToMessage(msg, "auto")) }) + onMount(() => { + if (typeof window !== "undefined" && "scrollRestoration" in window.history) { + window.history.scrollRestoration = "manual" + } + }) + return { clearMessageHash, scrollToMessage, diff --git a/packages/ui/package.json b/packages/ui/package.json index 4022deb4ae..ab1f09af3a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -48,11 +48,8 @@ "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", "@solid-primitives/bounds": "0.1.3", - "@solid-primitives/lifecycle": "0.1.2", "@solid-primitives/media": "2.3.3", - "@solid-primitives/page-visibility": "2.1.1", "@solid-primitives/resize-observer": "2.1.3", - "@solid-primitives/rootless": "1.5.2", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "dompurify": "3.3.1", diff --git a/packages/ui/src/components/animated-number.css b/packages/ui/src/components/animated-number.css index b69ce65084..022b347e96 100644 --- a/packages/ui/src/components/animated-number.css +++ b/packages/ui/src/components/animated-number.css @@ -9,20 +9,19 @@ display: inline-flex; flex-direction: row-reverse; align-items: baseline; - justify-content: flex-start; + justify-content: flex-end; line-height: inherit; width: var(--animated-number-width, 1ch); - overflow: clip; - transition: width var(--tool-motion-spring-ms, 800ms) var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); + overflow: hidden; + transition: width var(--tool-motion-spring-ms, 560ms) var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); } [data-slot="animated-number-digit"] { display: inline-block; - flex-shrink: 0; width: 1ch; height: 1em; line-height: 1em; - overflow: clip; + overflow: hidden; vertical-align: baseline; -webkit-mask-image: linear-gradient( to bottom, @@ -47,7 +46,7 @@ flex-direction: column; transform: translateY(calc(var(--animated-number-offset, 10) * -1em)); transition-property: transform; - transition-duration: var(--animated-number-duration, 600ms); + transition-duration: var(--animated-number-duration, 560ms); transition-timing-function: var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); } diff --git a/packages/ui/src/components/animated-number.tsx b/packages/ui/src/components/animated-number.tsx index dfe368b8bb..b5fceba256 100644 --- a/packages/ui/src/components/animated-number.tsx +++ b/packages/ui/src/components/animated-number.tsx @@ -1,7 +1,7 @@ import { For, Index, createEffect, createMemo, createSignal, on } from "solid-js" const TRACK = Array.from({ length: 30 }, (_, index) => index % 10) -const DURATION = 800 +const DURATION = 600 function normalize(value: number) { return ((value % 10) + 10) % 10 @@ -90,35 +90,10 @@ export function AnimatedNumber(props: { value: number; class?: string }) { ) const width = createMemo(() => `${digits().length}ch`) - const [exitingDigits, setExitingDigits] = createSignal([]) - let exitTimer: number | undefined - - createEffect( - on( - digits, - (current, prev) => { - if (prev && current.length < prev.length) { - setExitingDigits(prev.slice(current.length)) - clearTimeout(exitTimer) - exitTimer = window.setTimeout(() => setExitingDigits([]), DURATION) - } else { - clearTimeout(exitTimer) - setExitingDigits([]) - } - }, - { defer: true }, - ), - ) - - const displayDigits = createMemo(() => { - const exiting = exitingDigits() - return exiting.length ? [...digits(), ...exiting] : digits() - }) - return ( - {(digit) => } + {(digit) => } ) diff --git a/packages/ui/src/components/basic-tool.css b/packages/ui/src/components/basic-tool.css index ad25bef32b..1dbfce26ec 100644 --- a/packages/ui/src/components/basic-tool.css +++ b/packages/ui/src/components/basic-tool.css @@ -8,28 +8,54 @@ justify-content: flex-start; [data-slot="basic-tool-tool-trigger-content"] { - width: 100%; - min-width: 0; + width: auto; display: flex; align-items: center; align-self: stretch; gap: 8px; } + [data-slot="basic-tool-tool-indicator"] { + width: 16px; + height: 16px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + [data-component="spinner"] { + width: 16px; + height: 16px; + } + } + + [data-slot="basic-tool-tool-spinner"] { + width: 16px; + height: 16px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--text-weak); + + [data-component="spinner"] { + width: 16px; + height: 16px; + } + } + [data-slot="icon-svg"] { flex-shrink: 0; } [data-slot="basic-tool-tool-info"] { - flex: 1 1 auto; + flex: 0 1 auto; min-width: 0; font-size: 14px; } [data-slot="basic-tool-tool-info-structured"] { width: auto; - max-width: 100%; - min-width: 0; display: flex; align-items: center; gap: 8px; @@ -37,12 +63,11 @@ } [data-slot="basic-tool-tool-info-main"] { - flex: 0 1 auto; display: flex; - align-items: center; + align-items: baseline; gap: 8px; min-width: 0; - overflow: clip; + overflow: hidden; } [data-slot="basic-tool-tool-title"] { @@ -54,14 +79,22 @@ line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); color: var(--text-strong); + + &.capitalize { + text-transform: capitalize; + } + + &.agent-title { + color: var(--text-strong); + font-weight: var(--font-weight-medium); + } } [data-slot="basic-tool-tool-subtitle"] { - display: inline-block; - flex: 0 1 auto; - max-width: 100%; + flex-shrink: 1; min-width: 0; - overflow: clip; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-family-sans); font-variant-numeric: tabular-nums; @@ -106,7 +139,8 @@ [data-slot="basic-tool-tool-arg"] { flex-shrink: 1; min-width: 0; - overflow: clip; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-family-sans); font-variant-numeric: tabular-nums; diff --git a/packages/ui/src/components/basic-tool.tsx b/packages/ui/src/components/basic-tool.tsx index 3210b48701..4ad91824da 100644 --- a/packages/ui/src/components/basic-tool.tsx +++ b/packages/ui/src/components/basic-tool.tsx @@ -1,20 +1,8 @@ -import { - createEffect, - createSignal, - For, - Match, - on, - onCleanup, - onMount, - Show, - splitProps, - Switch, - type JSX, -} from "solid-js" -import { animate, type AnimationPlaybackControls, tunableSpringValue, COLLAPSIBLE_SPRING } from "./motion" +import { createEffect, createSignal, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js" +import { animate, type AnimationPlaybackControls } from "motion" import { Collapsible } from "./collapsible" +import type { IconProps } from "./icon" import { TextShimmer } from "./text-shimmer" -import { hold } from "./tool-utils" export type TriggerTitle = { title: string @@ -32,99 +20,26 @@ const isTriggerTitle = (val: any): val is TriggerTitle => { ) } -interface ToolCallPanelBaseProps { - icon: string +export interface BasicToolProps { + icon: IconProps["name"] trigger: TriggerTitle | JSX.Element children?: JSX.Element status?: string - animate?: boolean hideDetails?: boolean defaultOpen?: boolean forceOpen?: boolean defer?: boolean locked?: boolean - watchDetails?: boolean - springContent?: boolean + animated?: boolean onSubtitleClick?: () => void } -function ToolCallTriggerBody(props: { - trigger: TriggerTitle | JSX.Element - pending: boolean - onSubtitleClick?: () => void - arrow?: boolean -}) { - return ( -
-
-
- - - {(trigger) => ( -
-
- - - - - - { - if (!props.onSubtitleClick) return - e.stopPropagation() - props.onSubtitleClick() - }} - > - {trigger().subtitle} - - - - - {(arg) => ( - - {arg} - - )} - - - -
- {trigger().action} -
- )} -
- {props.trigger as JSX.Element} -
-
-
- - - -
- ) -} +const SPRING = { type: "spring" as const, visualDuration: 0.35, bounce: 0 } -function ToolCallPanel(props: ToolCallPanelBaseProps) { +export function BasicTool(props: BasicToolProps) { const [open, setOpen] = createSignal(props.defaultOpen ?? false) const [ready, setReady] = createSignal(open()) - const pendingRaw = () => props.status === "pending" || props.status === "running" - const pending = hold(pendingRaw, 1000) - const watchDetails = () => props.watchDetails !== false + const pending = () => props.status === "pending" || props.status === "running" let frame: number | undefined @@ -144,7 +59,7 @@ function ToolCallPanel(props: ToolCallPanelBaseProps) { on( open, (value) => { - if (!props.defer || props.springContent) return + if (!props.defer) return if (!value) { cancel() setReady(false) @@ -162,110 +77,36 @@ function ToolCallPanel(props: ToolCallPanelBaseProps) { ), ) - // Animated content height — single springValue drives all height changes + // Animated height for collapsible open/close let contentRef: HTMLDivElement | undefined - let bodyRef: HTMLDivElement | undefined - let fadeAnim: AnimationPlaybackControls | undefined - let observer: ResizeObserver | undefined - let resizeFrame: number | undefined + let heightAnim: AnimationPlaybackControls | undefined const initialOpen = open() - const heightSpring = tunableSpringValue(0, COLLAPSIBLE_SPRING) - - const read = () => Math.max(0, Math.ceil(bodyRef?.getBoundingClientRect().height ?? 0)) - - const doOpen = () => { - if (!contentRef || !bodyRef) return - contentRef.style.display = "" - // Ensure fade starts from 0 if content was hidden (first open or after close cleared styles) - if (bodyRef.style.opacity === "") { - bodyRef.style.opacity = "0" - bodyRef.style.filter = "blur(2px)" - } - const next = read() - fadeAnim?.stop() - fadeAnim = animate(bodyRef, { opacity: 1, filter: "blur(0px)" }, COLLAPSIBLE_SPRING) - fadeAnim.finished.then(() => { - if (!bodyRef) return - bodyRef.style.opacity = "" - bodyRef.style.filter = "" - }) - heightSpring.set(next) - } - - const doClose = () => { - if (!contentRef || !bodyRef) return - fadeAnim?.stop() - fadeAnim = animate(bodyRef, { opacity: 0, filter: "blur(2px)" }, COLLAPSIBLE_SPRING) - fadeAnim.finished.then(() => { - if (!contentRef || open()) return - contentRef.style.display = "none" - }) - heightSpring.set(0) - } - - const grow = () => { - if (!contentRef || !open()) return - const next = read() - if (Math.abs(next - heightSpring.get()) < 1) return - heightSpring.set(next) - } - - onMount(() => { - if (!props.springContent || props.animate === false || !contentRef || !bodyRef) return - - const offChange = heightSpring.on("change", (v) => { - if (!contentRef) return - contentRef.style.height = `${Math.max(0, Math.ceil(v))}px` - }) - onCleanup(() => { - offChange() - }) - - if (watchDetails()) { - observer = new ResizeObserver(() => { - if (resizeFrame !== undefined) return - resizeFrame = requestAnimationFrame(() => { - resizeFrame = undefined - grow() - }) - }) - observer.observe(bodyRef) - } - - if (!open()) return - if (contentRef.style.display !== "none") { - const next = read() - heightSpring.jump(next) - contentRef.style.height = `${next}px` - return - } - let mountFrame: number | undefined = requestAnimationFrame(() => { - mountFrame = undefined - if (!open()) return - doOpen() - }) - onCleanup(() => { - if (mountFrame !== undefined) cancelAnimationFrame(mountFrame) - }) - }) createEffect( on( open, (isOpen) => { - if (!props.springContent || props.animate === false || !contentRef) return - if (isOpen) doOpen() - else doClose() + if (!props.animated || !contentRef) return + heightAnim?.stop() + if (isOpen) { + contentRef.style.overflow = "hidden" + heightAnim = animate(contentRef, { height: "auto" }, SPRING) + heightAnim.finished.then(() => { + if (!contentRef || !open()) return + contentRef.style.overflow = "visible" + contentRef.style.height = "auto" + }) + } else { + contentRef.style.overflow = "hidden" + heightAnim = animate(contentRef, { height: "0px" }, SPRING) + } }, { defer: true }, ), ) onCleanup(() => { - if (resizeFrame !== undefined) cancelAnimationFrame(resizeFrame) - observer?.disconnect() - fadeAnim?.stop() - heightSpring.destroy() + heightAnim?.stop() }) const handleOpenChange = (value: boolean) => { @@ -277,34 +118,85 @@ function ToolCallPanel(props: ToolCallPanelBaseProps) { return ( - +
+
+
+ + + {(trigger) => ( +
+
+ + + + + + { + if (props.onSubtitleClick) { + e.stopPropagation() + props.onSubtitleClick() + } + }} + > + {trigger().subtitle} + + + + + {(arg) => ( + + {arg} + + )} + + + +
+ {trigger().action} +
+ )} +
+ {props.trigger as JSX.Element} +
+
+
+ + + +
- +
-
- {props.children} -
+ {props.children}
- + - -
{props.children}
-
+ {props.children}
@@ -330,60 +222,6 @@ function args(input: Record | undefined) { .slice(0, 3) } -export interface ToolCallRowProps { - variant: "row" - icon: string - trigger: TriggerTitle | JSX.Element - status?: string - animate?: boolean - onSubtitleClick?: () => void - open?: boolean - showArrow?: boolean - onOpenChange?: (value: boolean) => void -} -export interface ToolCallPanelProps extends Omit { - variant: "panel" -} -export type ToolCallProps = ToolCallRowProps | ToolCallPanelProps -function ToolCallRoot(props: ToolCallProps) { - const pending = () => props.status === "pending" || props.status === "running" - if (props.variant === "row") { - return ( - -
- -
-
- } - > - {(onOpenChange) => ( - - - - - - )} - - ) - } - - const [, rest] = splitProps(props, ["variant"]) - return -} -export const ToolCall = ToolCallRoot - export function GenericTool(props: { tool: string status?: string @@ -391,8 +229,7 @@ export function GenericTool(props: { input?: Record }) { return ( - ) } diff --git a/packages/ui/src/components/collapsible.css b/packages/ui/src/components/collapsible.css index 1a86338bdc..bab2c4f926 100644 --- a/packages/ui/src/components/collapsible.css +++ b/packages/ui/src/components/collapsible.css @@ -8,18 +8,14 @@ border-radius: var(--radius-md); overflow: visible; - &.tool-collapsible [data-slot="collapsible-trigger"] { - height: 37px; - } - - &.tool-collapsible [data-slot="basic-tool-content-inner"] { - padding-top: 0; + &.tool-collapsible { + gap: 8px; } [data-slot="collapsible-trigger"] { width: 100%; display: flex; - height: 36px; + height: 32px; padding: 0; align-items: center; align-self: stretch; @@ -27,17 +23,6 @@ user-select: none; color: var(--text-base); - > [data-component="tool-trigger"][data-arrow] { - width: auto; - max-width: 100%; - flex: 0 1 auto; - - [data-slot="basic-tool-tool-trigger-content"] { - width: auto; - max-width: 100%; - } - } - [data-slot="collapsible-arrow"] { opacity: 0; transition: opacity 0.15s ease; @@ -65,6 +50,9 @@ line-height: var(--line-height-large); /* 166.667% */ letter-spacing: var(--letter-spacing-normal); + /* &:hover { */ + /* background-color: var(--surface-base); */ + /* } */ &:focus-visible { outline: none; background-color: var(--surface-raised-base-hover); @@ -94,16 +82,16 @@ } [data-slot="collapsible-content"] { - overflow: clip; + overflow: hidden; + /* animation: slideUp 250ms ease-out; */ &[data-expanded] { overflow: visible; } - /* JS-animated content: overflow managed by animate() */ - &[data-spring-content] { - overflow: clip; - } + /* &[data-expanded] { */ + /* animation: slideDown 250ms ease-out; */ + /* } */ } &[data-variant="ghost"] { @@ -115,6 +103,9 @@ border: none; padding: 0; + /* &:hover { */ + /* color: var(--text-strong); */ + /* } */ &:focus-visible { outline: none; background-color: var(--surface-raised-base-hover); @@ -131,3 +122,21 @@ } } } + +@keyframes slideDown { + from { + height: 0; + } + to { + height: var(--kb-collapsible-content-height); + } +} + +@keyframes slideUp { + from { + height: var(--kb-collapsible-content-height); + } + to { + height: 0; + } +} diff --git a/packages/ui/src/components/context-tool-results.tsx b/packages/ui/src/components/context-tool-results.tsx deleted file mode 100644 index a0d9311de4..0000000000 --- a/packages/ui/src/components/context-tool-results.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { createMemo, createSignal, For, onMount } from "solid-js" -import type { ToolPart } from "@opencode-ai/sdk/v2" -import { getFilename } from "@opencode-ai/util/path" -import { useReducedMotion } from "../hooks/use-reduced-motion" -import { useI18n } from "../context/i18n" -import { ToolCall } from "./basic-tool" -import { ToolStatusTitle } from "./tool-status-title" -import { AnimatedCountList } from "./tool-count-summary" -import { RollingResults } from "./rolling-results" -import { GROW_SPRING } from "./motion" -import { useSpring } from "./motion-spring" -import { busy, updateScrollMask, useCollapsible, useRowWipe } from "./tool-utils" - -function contextToolLabel(part: ToolPart): { action: string; detail: string } { - const state = part.state - const title = "title" in state ? (state.title as string | undefined) : undefined - const input = state.input - if (part.tool === "read") { - const path = input?.filePath as string | undefined - return { action: "Read", detail: title || (path ? getFilename(path) : "") } - } - if (part.tool === "grep") { - const pattern = input?.pattern as string | undefined - return { action: "Search", detail: title || (pattern ? `"${pattern}"` : "") } - } - if (part.tool === "glob") { - const pattern = input?.pattern as string | undefined - return { action: "Find", detail: title || (pattern ?? "") } - } - if (part.tool === "list") { - const path = input?.path as string | undefined - return { action: "List", detail: title || (path ? getFilename(path) : "") } - } - return { action: part.tool, detail: title || "" } -} - -function contextToolSummary(parts: ToolPart[]) { - let read = 0 - let search = 0 - let list = 0 - for (const part of parts) { - if (part.tool === "read") read++ - else if (part.tool === "glob" || part.tool === "grep") search++ - else if (part.tool === "list") list++ - } - return { read, search, list } -} - -export function ContextToolGroupHeader(props: { - parts: ToolPart[] - pending: boolean - open: boolean - onOpenChange: (value: boolean) => void -}) { - const i18n = useI18n() - const summary = createMemo(() => contextToolSummary(props.parts)) - return ( - - - - - - - - - -
- } - /> - ) -} - -export function ContextToolExpandedList(props: { parts: ToolPart[]; expanded: boolean }) { - let contentRef: HTMLDivElement | undefined - let bodyRef: HTMLDivElement | undefined - let scrollRef: HTMLDivElement | undefined - const updateMask = () => { - if (scrollRef) updateScrollMask(scrollRef) - } - - useCollapsible({ - content: () => contentRef, - body: () => bodyRef, - open: () => props.expanded, - onOpen: updateMask, - }) - - return ( -
-
-
- - {(part) => { - const label = createMemo(() => contextToolLabel(part)) - return ( -
- {label().action} - {label().detail} -
- ) - }} -
-
-
-
- ) -} - -export function ContextToolRollingResults(props: { parts: ToolPart[]; pending: boolean }) { - const reduce = useReducedMotion() - const wiped = new Set() - const [mounted, setMounted] = createSignal(false) - onMount(() => setMounted(true)) - const show = () => mounted() && props.pending - const opacity = useSpring(() => (show() ? 1 : 0), GROW_SPRING) - const blur = useSpring(() => (show() ? 0 : 2), GROW_SPRING) - return ( -
- part.callID || part.id} - render={(part) => { - const label = createMemo(() => contextToolLabel(part)) - const k = part.callID || part.id - return ( -
- {label().action} - {(() => { - const [detailRef, setDetailRef] = createSignal() - useRowWipe({ - id: () => k, - text: () => label().detail, - ref: detailRef, - seen: wiped, - }) - return ( - - {label().detail} - - ) - })()} -
- ) - }} - /> -
- ) -} diff --git a/packages/ui/src/components/grow-box.tsx b/packages/ui/src/components/grow-box.tsx deleted file mode 100644 index c8ea6f3b3a..0000000000 --- a/packages/ui/src/components/grow-box.tsx +++ /dev/null @@ -1,432 +0,0 @@ -import { createEffect, on, type JSX, onMount, onCleanup } from "solid-js" -import { useReducedMotion } from "../hooks/use-reduced-motion" -import { animate, tunableSpringValue, type AnimationPlaybackControls, GROW_SPRING, type SpringConfig } from "./motion" - -export interface GrowBoxProps { - children: JSX.Element - /** Enable animation. When false, content shows immediately at full height. */ - animate?: boolean - /** Animate height from 0 to content height. Default: true. */ - grow?: boolean - /** Keep watching body size and animate subsequent height changes. Default: false. */ - watch?: boolean - /** Fade in body content (opacity + blur). Default: true. */ - fade?: boolean - /** Top padding in px on the body wrapper. Default: 0. */ - gap?: number - /** Reset to height:auto after grow completes, or stay at fixed px. Default: true. */ - autoHeight?: boolean - /** Controlled visibility for animating open/close without unmounting children. */ - open?: boolean - /** Animate controlled open/close changes after mount. Default: true. */ - animateToggle?: boolean - /** data-slot attribute on the root div. */ - slot?: string - /** CSS class on the root div. */ - class?: string - /** Override mount and resize spring config. Default: GROW_SPRING. */ - spring?: SpringConfig - /** Override controlled open/close spring config. Default: spring. */ - toggleSpring?: SpringConfig - /** Show a temporary bottom edge fade while height animation is running. */ - edge?: boolean - /** Edge fade height in px. Default: 20. */ - edgeHeight?: number - /** Edge fade opacity (0-1). Default: 1. */ - edgeOpacity?: number - /** Delay before edge fades out after height settles. Default: 320. */ - edgeIdle?: number - /** Edge fade-out duration in seconds. Default: 0.24. */ - edgeFade?: number - /** Edge fade-in duration in seconds. Default: 0.2. */ - edgeRise?: number -} - -/** - * Wraps children in a container that animates from zero height on mount. - * - * Includes a ResizeObserver so content changes after mount are also spring-animated. - * Used for timeline turns, assistant part groups, and user messages. - */ -export function GrowBox(props: GrowBoxProps) { - const reduce = useReducedMotion() - const spring = () => props.spring ?? GROW_SPRING - const toggleSpring = () => props.toggleSpring ?? spring() - let mode: "mount" | "toggle" = "mount" - let root: HTMLDivElement | undefined - let body: HTMLDivElement | undefined - let fadeAnim: AnimationPlaybackControls | undefined - let edgeRef: HTMLDivElement | undefined - let edgeAnim: AnimationPlaybackControls | undefined - let edgeTimer: ReturnType | undefined - let edgeOn = false - let mountFrame: number | undefined - let resizeFrame: number | undefined - let observer: ResizeObserver | undefined - let springTarget = -1 - const height = tunableSpringValue(0, { - type: "spring", - get visualDuration() { - return (mode === "toggle" ? toggleSpring() : spring()).visualDuration - }, - get bounce() { - return (mode === "toggle" ? toggleSpring() : spring()).bounce - }, - }) - - const gap = () => Math.max(0, props.gap ?? 0) - const grow = () => props.grow !== false - const watch = () => props.watch === true - const open = () => props.open !== false - const animateToggle = () => props.animateToggle !== false - const edge = () => props.edge === true - const edgeHeight = () => Math.max(0, props.edgeHeight ?? 20) - const edgeOpacity = () => Math.min(1, Math.max(0, props.edgeOpacity ?? 1)) - const edgeIdle = () => Math.max(0, props.edgeIdle ?? 320) - const edgeFade = () => Math.max(0.05, props.edgeFade ?? 0.24) - const edgeRise = () => Math.max(0.05, props.edgeRise ?? 0.2) - const animated = () => props.animate !== false && !reduce() - const edgeReady = () => animated() && open() && edge() && edgeHeight() > 0 - - const stopEdgeTimer = () => { - if (edgeTimer === undefined) return - clearTimeout(edgeTimer) - edgeTimer = undefined - } - - const hideEdge = (instant = false) => { - stopEdgeTimer() - if (!edgeRef) { - edgeOn = false - return - } - edgeAnim?.stop() - edgeAnim = undefined - if (instant || reduce()) { - edgeRef.style.opacity = "0" - edgeOn = false - return - } - if (!edgeOn) { - edgeRef.style.opacity = "0" - return - } - const current = animate(edgeRef, { opacity: 0 }, { type: "spring", visualDuration: edgeFade(), bounce: 0 }) - edgeAnim = current - current.finished - .catch(() => {}) - .finally(() => { - if (edgeAnim !== current) return - edgeAnim = undefined - if (!edgeRef) return - edgeRef.style.opacity = "0" - edgeOn = false - }) - } - - const showEdge = () => { - stopEdgeTimer() - if (!edgeRef) return - if (reduce()) { - edgeRef.style.opacity = `${edgeOpacity()}` - edgeOn = true - return - } - if (edgeOn && edgeAnim === undefined) { - edgeRef.style.opacity = `${edgeOpacity()}` - return - } - edgeAnim?.stop() - edgeAnim = undefined - if (!edgeOn) edgeRef.style.opacity = "0" - const current = animate( - edgeRef, - { opacity: edgeOpacity() }, - { type: "spring", visualDuration: edgeRise(), bounce: 0 }, - ) - edgeAnim = current - edgeOn = true - current.finished - .catch(() => {}) - .finally(() => { - if (edgeAnim !== current) return - edgeAnim = undefined - if (!edgeRef) return - edgeRef.style.opacity = `${edgeOpacity()}` - }) - } - - const queueEdgeHide = () => { - stopEdgeTimer() - if (!edgeOn) return - if (edgeIdle() <= 0) { - hideEdge() - return - } - edgeTimer = setTimeout(() => { - edgeTimer = undefined - hideEdge() - }, edgeIdle()) - } - - const hideBody = () => { - if (!body) return - body.style.opacity = "0" - body.style.filter = "blur(2px)" - } - - const clearBody = () => { - if (!body) return - body.style.opacity = "" - body.style.filter = "" - } - - const fadeBodyIn = (nextMode: "mount" | "toggle" = "mount") => { - if (props.fade === false || !body) return - if (reduce()) { - clearBody() - return - } - hideBody() - fadeAnim?.stop() - fadeAnim = animate(body, { opacity: 1, filter: "blur(0px)" }, nextMode === "toggle" ? toggleSpring() : spring()) - fadeAnim.finished.then(() => { - if (!body || !open()) return - clearBody() - }) - } - - const setInstant = (visible: boolean) => { - const next = visible ? targetHeight() : 0 - springTarget = next - height.jump(next) - root!.style.height = visible ? "" : "0px" - root!.style.overflow = visible ? "" : "clip" - hideEdge(true) - if (visible || props.fade === false) clearBody() - else hideBody() - } - - const currentHeight = () => { - if (!root) return 0 - const v = root.style.height - if (v && v !== "auto") { - const n = Number.parseFloat(v) - if (!Number.isNaN(n)) return n - } - return Math.max(0, root.getBoundingClientRect().height) - } - - const targetHeight = () => Math.max(0, Math.ceil(body?.getBoundingClientRect().height ?? 0)) - - const setHeight = (nextMode: "mount" | "toggle" = "mount") => { - if (!root || !open()) return - const next = targetHeight() - if (reduce()) { - springTarget = next - height.jump(next) - if (props.autoHeight === false || watch()) { - root.style.height = `${next}px` - root.style.overflow = next > 0 ? "visible" : "clip" - return - } - root.style.height = "auto" - root.style.overflow = next > 0 ? "visible" : "clip" - return - } - if (next === springTarget) return - const prev = currentHeight() - if (Math.abs(next - prev) < 1) { - springTarget = next - if (props.autoHeight === false || watch()) { - root.style.height = `${next}px` - root.style.overflow = next > 0 ? "visible" : "clip" - } - return - } - root.style.overflow = "clip" - springTarget = next - mode = nextMode - height.set(next) - } - - onMount(() => { - if (!root || !body) return - - const offChange = height.on("change", (next) => { - if (!root) return - root.style.height = `${Math.max(0, next)}px` - }) - const offStart = height.on("animationStart", () => { - if (!root) return - root.style.overflow = "clip" - root.style.willChange = "height" - root.style.contain = "layout style" - if (edgeReady()) showEdge() - }) - const offComplete = height.on("animationComplete", () => { - if (!root) return - root.style.willChange = "" - root.style.contain = "" - if (!open()) { - springTarget = 0 - root.style.height = "0px" - root.style.overflow = "clip" - return - } - const next = targetHeight() - springTarget = next - if (props.autoHeight === false || watch()) { - root.style.height = `${next}px` - root.style.overflow = next > 0 ? "visible" : "clip" - if (edgeReady()) queueEdgeHide() - return - } - root.style.height = "auto" - root.style.overflow = "visible" - if (edgeReady()) queueEdgeHide() - }) - - onCleanup(() => { - offComplete() - offStart() - offChange() - }) - - if (watch()) { - observer = new ResizeObserver(() => { - if (!open()) return - if (resizeFrame !== undefined) return - resizeFrame = requestAnimationFrame(() => { - resizeFrame = undefined - setHeight("mount") - }) - }) - observer.observe(body) - } - - if (!animated()) { - setInstant(open()) - return - } - - if (props.fade !== false) hideBody() - hideEdge(true) - - if (!open()) { - root.style.height = "0px" - root.style.overflow = "clip" - } else { - if (grow()) { - root.style.height = "0px" - root.style.overflow = "clip" - } else { - root.style.height = "auto" - root.style.overflow = "visible" - } - mountFrame = requestAnimationFrame(() => { - mountFrame = undefined - fadeBodyIn("mount") - if (grow()) setHeight("mount") - }) - } - }) - - createEffect( - on( - () => props.open, - (value) => { - if (value === undefined) return - if (!root || !body) return - if (!animateToggle() || reduce()) { - setInstant(value) - return - } - fadeAnim?.stop() - if (!value) hideEdge(true) - if (!value) { - const next = currentHeight() - if (Math.abs(next - height.get()) >= 1) { - springTarget = next - height.jump(next) - root.style.height = `${next}px` - } - if (props.fade !== false) { - fadeAnim = animate(body, { opacity: 0, filter: "blur(2px)" }, toggleSpring()) - } - root.style.overflow = "clip" - springTarget = 0 - mode = "toggle" - height.set(0) - return - } - fadeBodyIn("toggle") - setHeight("toggle") - }, - { defer: true }, - ), - ) - - createEffect(() => { - if (!edgeRef) return - edgeRef.style.height = `${edgeHeight()}px` - if (!animated() || !open() || edgeHeight() <= 0) { - hideEdge(true) - return - } - if (edge()) return - hideEdge() - }) - - createEffect(() => { - if (!root || !body) return - if (!reduce()) return - fadeAnim?.stop() - edgeAnim?.stop() - setInstant(open()) - }) - - onCleanup(() => { - stopEdgeTimer() - if (mountFrame !== undefined) cancelAnimationFrame(mountFrame) - if (resizeFrame !== undefined) cancelAnimationFrame(resizeFrame) - observer?.disconnect() - height.destroy() - fadeAnim?.stop() - edgeAnim?.stop() - edgeAnim = undefined - edgeOn = false - }) - - return ( -
-
0 ? `${gap()}px` : undefined }}> - {props.children} -
-
-
- ) -} diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 9a6784d702..8fc7090133 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -1,20 +1,10 @@ [data-component="assistant-message"] { content-visibility: auto; width: 100%; -} - -[data-component="assistant-parts"] { - width: 100%; - min-width: 0; display: flex; flex-direction: column; align-items: flex-start; - gap: 0; -} - -[data-component="assistant-part-item"] { - width: 100%; - min-width: 0; + gap: 12px; } [data-component="user-message"] { @@ -37,14 +27,6 @@ color: var(--text-weak); } - [data-slot="user-message-inner"] { - position: relative; - display: flex; - flex-direction: column; - align-items: flex-end; - width: 100%; - gap: 4px; - } [data-slot="user-message-attachments"] { display: flex; flex-wrap: wrap; @@ -53,7 +35,6 @@ width: fit-content; max-width: min(82%, 64ch); margin-left: auto; - margin-bottom: 4px; } [data-slot="user-message-attachment"] { @@ -153,7 +134,7 @@ [data-slot="user-message-copy-wrapper"] { min-height: 24px; - margin-top: 0; + margin-top: 4px; display: flex; align-items: center; justify-content: flex-end; @@ -163,6 +144,7 @@ pointer-events: none; transition: opacity 0.15s ease; will-change: opacity; + [data-component="tooltip-trigger"] { display: inline-flex; width: fit-content; @@ -205,21 +187,56 @@ opacity: 1; pointer-events: auto; } + + .text-text-strong { + color: var(--text-strong); + } + + .font-medium { + font-weight: var(--font-weight-medium); + } } [data-component="text-part"] { width: 100%; - margin-top: 0; - padding-block: 4px; - position: relative; + margin-top: 24px; [data-slot="text-part-body"] { margin-top: 0; } - [data-slot="text-part-turn-summary"] { + [data-slot="text-part-copy-wrapper"] { + min-height: 24px; + margin-top: 4px; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 10px; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; + will-change: opacity; + + [data-component="tooltip-trigger"] { + display: inline-flex; + width: fit-content; + } + } + + [data-slot="text-part-meta"] { + user-select: none; + } + + [data-slot="text-part-copy-wrapper"][data-interrupted] { width: 100%; - min-width: 0; + justify-content: flex-end; + gap: 12px; + } + + &:hover [data-slot="text-part-copy-wrapper"], + &:focus-within [data-slot="text-part-copy-wrapper"] { + opacity: 1; + pointer-events: auto; } [data-component="markdown"] { @@ -228,10 +245,6 @@ } } -[data-component="assistant-part-item"][data-kind="text"][data-last="true"] [data-component="text-part"] { - padding-bottom: 0; -} - [data-component="compaction-part"] { width: 100%; display: flex; @@ -265,6 +278,7 @@ line-height: var(--line-height-normal); [data-component="markdown"] { + margin-top: 24px; font-style: normal; font-size: inherit; color: var(--text-weak); @@ -358,16 +372,13 @@ height: auto; max-height: 240px; overflow-y: auto; - overscroll-behavior: contain; scrollbar-width: none; -ms-overflow-style: none; - -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 6px, black calc(100% - 6px), transparent 100%); - mask-image: linear-gradient(to bottom, transparent 0, black 6px, black calc(100% - 6px), transparent 100%); - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; + &::-webkit-scrollbar { display: none; } + [data-component="markdown"] { overflow: visible; } @@ -437,7 +448,7 @@ [data-component="write-trigger"] { display: flex; align-items: center; - justify-content: flex-start; + justify-content: space-between; gap: 8px; width: 100%; @@ -450,8 +461,7 @@ } [data-slot="message-part-title"] { - flex-shrink: 1; - min-width: 0; + flex-shrink: 0; display: flex; align-items: center; gap: 8px; @@ -483,45 +493,40 @@ [data-slot="message-part-title-text"] { text-transform: capitalize; color: var(--text-strong); - flex-shrink: 0; - } - - [data-slot="message-part-meta-line"], - .message-part-meta-line { - min-width: 0; - display: inline-flex; - align-items: center; - gap: 6px; - font-weight: var(--font-weight-regular); - - [data-component="diff-changes"] { - flex-shrink: 0; - gap: 6px; - } - } - - .message-part-meta-line.soft { - [data-slot="message-part-title-filename"] { - color: var(--text-base); - } } [data-slot="message-part-title-filename"] { /* No text-transform - preserve original filename casing */ - color: var(--text-strong); - flex-shrink: 0; + font-weight: var(--font-weight-regular); } - [data-slot="message-part-directory-inline"] { - color: var(--text-weak); + [data-slot="message-part-path"] { + display: flex; + flex-grow: 1; min-width: 0; - max-width: min(48vw, 36ch); + font-weight: var(--font-weight-regular); + } + + [data-slot="message-part-directory"] { + color: var(--text-weak); text-overflow: ellipsis; overflow: hidden; white-space: nowrap; direction: rtl; text-align: left; } + + [data-slot="message-part-filename"] { + color: var(--text-strong); + flex-shrink: 0; + } + + [data-slot="message-part-actions"] { + display: flex; + gap: 16px; + align-items: center; + justify-content: flex-end; + } } [data-component="edit-content"] { @@ -612,17 +617,6 @@ } } -[data-slot="webfetch-meta"] { - min-width: 0; - display: inline-flex; - align-items: center; - gap: 8px; - - [data-component="tool-action"] { - flex-shrink: 0; - } -} - [data-component="todos"] { padding: 10px 0 24px 0; display: flex; @@ -645,6 +639,7 @@ } [data-component="context-tool-group-trigger"] { + width: 100%; min-height: 24px; display: flex; align-items: center; @@ -652,352 +647,28 @@ gap: 0px; cursor: pointer; - &[data-pending] { - cursor: default; - } - [data-slot="context-tool-group-title"] { flex-shrink: 1; min-width: 0; } -} -/* Prevent the trigger content from stretching full-width so the arrow sits after the text */ -[data-slot="basic-tool-tool-trigger-content"]:has([data-component="context-tool-group-trigger"]) { - width: auto; - flex: 0 1 auto; - - [data-slot="basic-tool-tool-info"] { - flex: 0 1 auto; - } -} - -[data-component="context-tool-step"] { - width: 100%; - min-width: 0; - padding-left: 12px; -} - -[data-component="context-tool-expanded-list"] { - display: flex; - flex-direction: column; - padding: 4px 0 4px 12px; - max-height: 200px; - overflow-y: auto; - overscroll-behavior: contain; - scrollbar-width: none; - -ms-overflow-style: none; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - - &::-webkit-scrollbar { - display: none; - } -} - -[data-component="context-tool-expanded-row"] { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; - height: 22px; - flex-shrink: 0; - white-space: nowrap; - overflow: hidden; - - [data-slot="context-tool-expanded-action"] { - flex-shrink: 0; - font-size: var(--font-size-base); - font-weight: 500; - color: var(--text-base); - } - - [data-slot="context-tool-expanded-detail"] { - flex-shrink: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: var(--font-size-base); - color: var(--text-base); - opacity: 0.75; - } -} - -[data-component="context-tool-rolling-row"] { - display: inline-flex; - align-items: center; - gap: 6px; - width: 100%; - min-width: 0; - white-space: nowrap; - overflow: hidden; - padding-left: 12px; - - [data-slot="context-tool-rolling-action"] { - flex-shrink: 0; - font-size: var(--font-size-base); - font-weight: 500; - color: var(--text-base); - } - - [data-slot="context-tool-rolling-detail"] { - flex-shrink: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: var(--font-size-base); - color: var(--text-weak); - } -} - -[data-component="shell-rolling-results"] { - width: 100%; - min-width: 0; - display: flex; - flex-direction: column; - - [data-slot="shell-rolling-header-clip"] { - &:hover [data-slot="shell-rolling-actions"] { - opacity: 1; - } - - &[data-clickable="true"] { - cursor: pointer; - } - } - - [data-slot="shell-rolling-header"] { - display: inline-flex; - align-items: center; - gap: 8px; - min-width: 0; - max-width: 100%; - height: 37px; - box-sizing: border-box; - } - - [data-slot="shell-rolling-title"] { - flex-shrink: 0; - font-family: var(--font-family-sans); - font-size: 14px; - font-style: normal; - font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); - letter-spacing: var(--letter-spacing-normal); - color: var(--text-strong); - } - - [data-slot="shell-rolling-subtitle"] { - flex: 0 1 auto; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-family: var(--font-family-sans); - font-size: 14px; - font-weight: var(--font-weight-normal); - line-height: var(--line-height-large); - color: var(--text-weak); - } - - [data-slot="shell-rolling-actions"] { - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 2px; - opacity: 0; - transition: opacity 0.15s ease; - } - - .shell-rolling-copy { - border: none !important; - outline: none !important; - box-shadow: none !important; - background: transparent !important; - - [data-slot="icon-svg"] { - color: var(--icon-weaker); - } - - &:hover:not(:disabled) { - background: color-mix(in srgb, var(--text-base) 8%, transparent) !important; - box-shadow: 0 0 0 1px color-mix(in srgb, var(--icon-weaker) 40%, transparent) !important; - border-radius: var(--radius-sm); - - [data-slot="icon-svg"] { - color: var(--icon-base); - } - } - } - - [data-slot="shell-rolling-arrow"] { - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--icon-weaker); - transform: rotate(-90deg); - transition: transform 0.15s ease; - } - - [data-slot="shell-rolling-arrow"][data-open="true"] { - transform: rotate(0deg); - } -} - -[data-component="shell-rolling-output"] { - width: 100%; - min-width: 0; -} - -[data-slot="shell-rolling-preview"] { - width: 100%; - min-width: 0; -} - -[data-component="shell-expanded-output"] { - width: 100%; - max-width: 100%; - overflow-y: auto; - overflow-x: hidden; - scrollbar-width: none; - -ms-overflow-style: none; - - &::-webkit-scrollbar { - display: none; - } -} - -[data-component="shell-expanded-shell"] { - position: relative; - width: 100%; - min-width: 0; - border: 1px solid var(--border-weak-base); - border-radius: 6px; - background: transparent; - overflow: hidden; -} - -[data-slot="shell-expanded-body"] { - position: relative; - width: 100%; - min-width: 0; -} - -[data-slot="shell-expanded-top"] { - position: relative; - width: 100%; - min-width: 0; - padding: 9px 44px 9px 16px; - box-sizing: border-box; -} - -[data-slot="shell-expanded-command"] { - display: flex; - align-items: flex-start; - gap: 8px; - width: 100%; - min-width: 0; - font-family: var(--font-family-mono); - font-feature-settings: var(--font-family-mono--font-feature-settings); - font-size: 13px; - line-height: 1.45; -} - -[data-slot="shell-expanded-prompt"] { - flex-shrink: 0; - color: var(--text-weaker); -} - -[data-slot="shell-expanded-input"] { - min-width: 0; - color: var(--text-strong); - white-space: pre-wrap; - overflow-wrap: anywhere; -} - -[data-slot="shell-expanded-actions"] { - position: absolute; - top: 50%; - right: 8px; - z-index: 1; - transform: translateY(-50%); -} - -.shell-expanded-copy { - border: none !important; - outline: none !important; - box-shadow: none !important; - background: transparent !important; - - [data-slot="icon-svg"] { + [data-slot="collapsible-arrow"] { color: var(--icon-weaker); } +} - &:hover:not(:disabled) { - background: color-mix(in srgb, var(--text-base) 8%, transparent) !important; - box-shadow: 0 0 0 1px color-mix(in srgb, var(--icon-weaker) 40%, transparent) !important; - border-radius: var(--radius-sm); +[data-component="context-tool-group-list"] { + padding: 6px 0 4px 0; + display: flex; + flex-direction: column; + gap: 2px; - [data-slot="icon-svg"] { - color: var(--icon-base); - } + [data-slot="context-tool-group-item"] { + min-width: 0; + padding: 6px 0; } } -[data-slot="shell-expanded-divider"] { - width: 100%; - height: 1px; - background: var(--border-weak-base); -} - -[data-slot="shell-expanded-pre"] { - margin: 0; - padding: 12px 16px; - white-space: pre-wrap; - overflow-wrap: anywhere; - - code { - font-family: var(--font-family-mono); - font-feature-settings: var(--font-family-mono--font-feature-settings); - font-size: 13px; - line-height: 1.45; - color: var(--text-base); - } -} - -[data-component="shell-rolling-command"], -[data-component="shell-rolling-row"] { - display: inline-flex; - align-items: center; - width: 100%; - min-width: 0; - overflow: hidden; - white-space: pre; - padding-left: 12px; -} - -[data-slot="shell-rolling-text"] { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-family: var(--font-family-mono); - font-feature-settings: var(--font-family-mono--font-feature-settings); - font-size: var(--font-size-small); - line-height: var(--line-height-large); -} - -[data-component="shell-rolling-command"] [data-slot="shell-rolling-text"] { - color: var(--text-base); -} - -[data-component="shell-rolling-command"] [data-slot="shell-rolling-prompt"] { - color: var(--text-weaker); -} - -[data-component="shell-rolling-row"] [data-slot="shell-rolling-text"] { - color: var(--text-weak); -} - [data-component="diagnostics"] { display: flex; flex-direction: column; @@ -1058,30 +729,6 @@ width: 100%; } -[data-slot="assistant-part-grow"] { - width: 100%; - min-width: 0; - overflow: visible; -} - -[data-component="tool-part-wrapper"][data-tool="bash"] { - [data-component="tool-trigger"] { - width: auto; - max-width: 100%; - } - - [data-slot="basic-tool-tool-info-main"] { - align-items: center; - } - - [data-slot="basic-tool-tool-title"], - [data-slot="basic-tool-tool-subtitle"] { - display: inline-flex; - align-items: center; - line-height: var(--line-height-large); - } -} - [data-component="dock-prompt"][data-kind="permission"] { position: relative; display: flex; @@ -1540,7 +1187,8 @@ position: sticky; top: var(--sticky-accordion-top, 0px); z-index: 20; - height: 37px; + height: 40px; + padding-bottom: 8px; background-color: var(--background-stronger); } } @@ -1551,12 +1199,11 @@ } [data-slot="apply-patch-trigger-content"] { - display: inline-flex; + display: flex; align-items: center; - justify-content: flex-start; - max-width: 100%; - min-width: 0; - gap: 8px; + justify-content: space-between; + width: 100%; + gap: 20px; } [data-slot="apply-patch-file-info"] { @@ -1590,9 +1237,9 @@ [data-slot="apply-patch-trigger-actions"] { flex-shrink: 0; display: flex; - gap: 8px; + gap: 16px; align-items: center; - justify-content: flex-start; + justify-content: flex-end; } [data-slot="apply-patch-change"] { @@ -1632,11 +1279,10 @@ } [data-component="tool-loaded-file"] { - min-width: 0; display: flex; align-items: center; gap: 8px; - padding: 4px 0 4px 12px; + padding: 4px 0 4px 28px; font-family: var(--font-family-sans); font-size: var(--font-size-small); font-weight: var(--font-weight-regular); @@ -1647,11 +1293,4 @@ flex-shrink: 0; color: var(--icon-weak); } - - span { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } } diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index d821211592..45b174e2b8 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -1,6 +1,18 @@ -import { Component, createEffect, createMemo, createSignal, For, Match, on, Show, Switch, type JSX } from "solid-js" +import { + Component, + createEffect, + createMemo, + createSignal, + For, + Match, + onMount, + Show, + Switch, + onCleanup, + Index, + type JSX, +} from "solid-js" import stripAnsi from "strip-ansi" -import { createStore } from "solid-js/store" import { Dynamic } from "solid-js/web" import { AgentPart, @@ -20,10 +32,11 @@ import { useData } from "../context" import { useFileComponent } from "../context/file" import { useDialog } from "../context/dialog" import { type UiI18n, useI18n } from "../context/i18n" -import { GenericTool, ToolCall } from "./basic-tool" +import { BasicTool, GenericTool } from "./basic-tool" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" import { Card } from "./card" +import { Collapsible } from "./collapsible" import { FileIcon } from "./file-icon" import { Icon } from "./icon" import { Checkbox } from "./checkbox" @@ -35,12 +48,43 @@ import { checksum } from "@opencode-ai/util/encode" import { Tooltip } from "./tooltip" import { IconButton } from "./icon-button" import { TextShimmer } from "./text-shimmer" -import { list } from "./text-utils" -import { GrowBox } from "./grow-box" -import { COLLAPSIBLE_SPRING } from "./motion" -import { busy, createThrottledValue, useToolFade, useContextToolPending } from "./tool-utils" -import { ContextToolGroupHeader, ContextToolExpandedList, ContextToolRollingResults } from "./context-tool-results" -import { ShellRollingResults } from "./shell-rolling-results" +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 + let valueRef: HTMLSpanElement | undefined + + onMount(() => { + if (!props.animate) return + requestAnimationFrame(() => { + if (widthRef) { + animate(widthRef, { width: "auto" }, { type: "spring", visualDuration: 0.25, bounce: 0 }) + } + if (valueRef) { + animate(valueRef, { opacity: 1, filter: "blur(0px)" }, { duration: 0.32, ease: [0.16, 1, 0.3, 1] }) + } + }) + }) + + return ( + + + + + {props.text} + + + + + ) +} interface Diagnostic { range: { @@ -81,22 +125,64 @@ function DiagnosticsDisplay(props: { diagnostics: Diagnostic[] }): JSX.Element { ) } +export interface MessageProps { + message: MessageType + parts: PartType[] + showAssistantCopyPartID?: string | null + interrupted?: boolean + queued?: boolean + showReasoningSummaries?: boolean +} + export interface MessagePartProps { part: PartType message: MessageType hideDetails?: boolean defaultOpen?: boolean showAssistantCopyPartID?: string | null - showTurnDiffSummary?: boolean - turnDiffSummary?: () => JSX.Element - animate?: boolean - working?: boolean + turnDurationMs?: number } export type PartComponent = Component export const PART_MAPPING: Record = {} +const TEXT_RENDER_THROTTLE_MS = 100 + +function createThrottledValue(getValue: () => string) { + const [value, setValue] = createSignal(getValue()) + let timeout: ReturnType | undefined + let last = 0 + + createEffect(() => { + const next = getValue() + const now = Date.now() + + const remaining = TEXT_RENDER_THROTTLE_MS - (now - last) + if (remaining <= 0) { + if (timeout) { + clearTimeout(timeout) + timeout = undefined + } + last = now + setValue(next) + return + } + if (timeout) clearTimeout(timeout) + timeout = setTimeout(() => { + last = Date.now() + setValue(next) + timeout = undefined + }, remaining) + }) + + onCleanup(() => { + if (timeout) clearTimeout(timeout) + }) + + return value +} + function relativizeProjectPath(path: string, directory?: string) { if (!path) return "" if (!directory) return path @@ -228,8 +314,7 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo { case "skill": return { icon: "brain", - title: i18n.t("ui.tool.skill"), - subtitle: typeof input.name === "string" ? input.name : undefined, + title: input.name || "skill", } default: return { @@ -254,22 +339,105 @@ function urls(text: string | undefined) { const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"]) const HIDDEN_TOOLS = new Set(["todowrite", "todoread"]) -function createGroupOpenState() { - const [state, setState] = createStore>({}) - const read = (key?: string, collapse?: boolean) => { - if (!key) return true - const value = state[key] - if (value !== undefined) return value - return !collapse +function list(value: T[] | undefined | null, fallback: T[]) { + if (Array.isArray(value)) return value + return fallback +} + +function same(a: readonly T[] | undefined, b: readonly T[] | undefined) { + if (a === b) return true + if (!a || !b) return false + if (a.length !== b.length) return false + return a.every((x, i) => x === b[i]) +} + +type PartRef = { + messageID: string + partID: string +} + +type PartGroup = + | { + key: string + type: "part" + ref: PartRef + } + | { + key: string + type: "context" + refs: PartRef[] + } + +function sameRef(a: PartRef, b: PartRef) { + return a.messageID === b.messageID && a.partID === b.partID +} + +function sameGroup(a: PartGroup, b: PartGroup) { + if (a === b) return true + if (a.key !== b.key) return false + if (a.type !== b.type) return false + if (a.type === "part") { + if (b.type !== "part") return false + return sameRef(a.ref, b.ref) } - const controlled = (key?: string) => { - if (!key) return false - return state[key] !== undefined + if (b.type !== "context") return false + if (a.refs.length !== b.refs.length) return false + return a.refs.every((ref, i) => sameRef(ref, b.refs[i]!)) +} + +function sameGroups(a: readonly PartGroup[] | undefined, b: readonly PartGroup[] | undefined) { + if (a === b) return true + if (!a || !b) return false + if (a.length !== b.length) return false + return a.every((item, i) => sameGroup(item, b[i]!)) +} + +function groupParts(parts: { messageID: string; part: PartType }[]) { + const result: PartGroup[] = [] + let start = -1 + + const flush = (end: number) => { + if (start < 0) return + const first = parts[start] + const last = parts[end] + if (!first || !last) { + start = -1 + return + } + result.push({ + key: `context:${first.part.id}`, + type: "context", + refs: parts.slice(start, end + 1).map((item) => ({ + messageID: item.messageID, + partID: item.part.id, + })), + }) + start = -1 } - const write = (key: string, value: boolean) => { - setState(key, value) - } - return { read, controlled, write } + + parts.forEach((item, index) => { + if (isContextGroupTool(item.part)) { + if (start < 0) start = index + return + } + + flush(index - 1) + result.push({ + key: `part:${item.messageID}:${item.part.id}`, + type: "part", + ref: { + messageID: item.messageID, + partID: item.part.id, + }, + }) + }) + + flush(parts.length - 1) + return result +} + +function partByID(parts: readonly PartType[], partID: string) { + return parts.find((part) => part.id === partID) } function renderable(part: PartType, showReasoningSummaries = true) { @@ -285,8 +453,7 @@ function renderable(part: PartType, showReasoningSummaries = true) { function toolDefaultOpen(tool: string, shell = false, edit = false) { if (tool === "bash") return shell - if (tool === "edit" || tool === "write") return edit - if (tool === "apply_patch") return false + if (tool === "edit" || tool === "write" || tool === "apply_patch") return edit } function partDefaultOpen(part: PartType, shell = false, edit = false) { @@ -294,323 +461,98 @@ function partDefaultOpen(part: PartType, shell = false, edit = false) { return toolDefaultOpen(part.tool, shell, edit) } -function PartGrow(props: { - children: JSX.Element - animate?: boolean - animateToggle?: boolean - gap?: number - fade?: boolean - edge?: boolean - edgeHeight?: number - edgeOpacity?: number - edgeIdle?: number - edgeFade?: number - edgeRise?: number - grow?: boolean - watch?: boolean - open?: boolean - spring?: import("./motion").SpringConfig - toggleSpring?: import("./motion").SpringConfig -}) { - return ( - - {props.children} - - ) -} - export function AssistantParts(props: { messages: AssistantMessage[] showAssistantCopyPartID?: string | null - showTurnDiffSummary?: boolean - turnDiffSummary?: () => JSX.Element + turnDurationMs?: number working?: boolean showReasoningSummaries?: boolean shellToolDefaultOpen?: boolean editToolDefaultOpen?: boolean - animate?: boolean }) { const data = useData() const emptyParts: PartType[] = [] - const groupState = createGroupOpenState() - const grouped = createMemo(() => { - const keys: string[] = [] - const items: Record< - string, - | { - type: "part" - part: PartType - message: AssistantMessage - context?: boolean - groupKey?: string - afterTool?: boolean - groupTail?: boolean - groupParts?: { part: ToolPart; message: AssistantMessage }[] - } - | { - type: "context" - groupKey: string - parts: { part: ToolPart; message: AssistantMessage }[] - tail: boolean - afterTool: boolean - } - > = {} - const push = (key: string, item: (typeof items)[string]) => { - keys.push(key) - items[key] = item - } - const id = (part: PartType) => { - if (part.type === "tool") return part.callID || part.id - return part.id - } - const parts = props.messages.flatMap((message) => - list(data.store.part?.[message.id], emptyParts) - .filter((part) => renderable(part, props.showReasoningSummaries ?? true)) - .map((part) => ({ message, part })), - ) + const emptyTools: ToolPart[] = [] - let start = -1 + const grouped = createMemo( + () => + groupParts( + props.messages.flatMap((message) => + list(data.store.part?.[message.id], emptyParts) + .filter((part) => renderable(part, props.showReasoningSummaries ?? true)) + .map((part) => ({ + messageID: message.id, + part, + })), + ), + ), + [] as PartGroup[], + { equals: sameGroups }, + ) - const flush = (end: number, tail: boolean, afterTool: boolean) => { - if (start < 0) return - const group = parts - .slice(start, end + 1) - .filter((entry): entry is { part: ToolPart; message: AssistantMessage } => isContextGroupTool(entry.part)) - if (!group.length) { - start = -1 - return - } - const groupKey = `context:${group[0].message.id}:${id(group[0].part)}` - push(groupKey, { - type: "context", - groupKey, - parts: group, - tail, - afterTool, - }) - group.forEach((entry) => { - push(`part:${entry.message.id}:${id(entry.part)}`, { - type: "part", - part: entry.part, - message: entry.message, - context: true, - groupKey, - afterTool, - groupTail: tail, - groupParts: group, - }) - }) - start = -1 - } - parts.forEach((item, index) => { - if (isContextGroupTool(item.part)) { - if (start < 0) start = index - return - } - - flush(index - 1, false, (item as { part: PartType }).part.type === "tool") - push(`part:${item.message.id}:${id(item.part)}`, { type: "part", part: item.part, message: item.message }) - }) - - flush(parts.length - 1, true, false) - return { keys, items } - }) - - const last = createMemo(() => grouped().keys.at(-1)) + const last = createMemo(() => grouped().at(-1)?.key) return ( -
- - {(key) => { - const item = createMemo(() => grouped().items[key]) - const ctx = createMemo(() => { - const value = item() - if (!value) return - if (value.type !== "context") return - return value - }) - const part = createMemo(() => { - const value = item() - if (!value) return - if (value.type !== "part") return - return value - }) - const tail = createMemo(() => last() === key) - const tool = createMemo(() => { - const value = part() - if (!value) return false - return value.part.type === "tool" - }) - const context = createMemo(() => !!part()?.context) - const contextSpring = createMemo(() => { - const entry = part() - if (!entry?.context) return undefined - if (!groupState.controlled(entry.groupKey)) return undefined - return COLLAPSIBLE_SPRING - }) - const contextOpen = createMemo(() => { - const value = ctx() - if (value) return groupState.read(value.groupKey, true) - return groupState.read(part()?.groupKey, true) - }) - const visible = createMemo(() => { - if (!context()) return true - if (ctx()) return true - return false - }) + + {(entryAccessor) => { + const entryType = createMemo(() => entryAccessor().type) - const turnSummary = createMemo(() => { - const value = part() - if (!value) return false - if (value.part.type !== "text") return false - if (!props.showTurnDiffSummary) return false - return props.showAssistantCopyPartID === value.part.id - }) - const fade = createMemo(() => { - if (ctx()) return true - return tool() - }) - const edge = createMemo(() => { - const entry = part() - if (!entry) return false - if (entry.part.type !== "text") return false - if (!props.working) return false - return tail() - }) - const watch = createMemo(() => !context() && !tool() && tail() && !turnSummary()) - const ctxPartsCache = new Map() - let ctxPartsPrev: ToolPart[] = [] - const ctxParts = createMemo(() => { - const parts = ctx()?.parts ?? [] - if (parts.length === 0 && ctxPartsPrev.length > 0) return ctxPartsPrev - const result: ToolPart[] = [] - for (const item of parts) { - const k = item.part.callID || item.part.id - const cached = ctxPartsCache.get(k) - if (cached) { - result.push(cached) - } else { - ctxPartsCache.set(k, item.part) - result.push(item.part) - } - } - ctxPartsPrev = result - return result - }) - const ctxPending = useContextToolPending(ctxParts, () => !!(props.working && ctx()?.tail)) - const shell = createMemo(() => { - const value = part() - if (!value) return - if (value.part.type !== "tool") return - if (value.part.tool !== "bash") return - return value.part - }) - const kind = createMemo(() => { - if (ctx()) return "context" - if (shell()) return "shell" - const value = part() - if (!value) return "part" - return value.part.type - }) - const shown = createMemo(() => { - if (ctx()) return true - if (shell()) return true - const entry = part() - if (!entry) return false - return !entry.context - }) - const partGrowProps = () => ({ - animate: props.animate, - gap: 0, - fade: fade(), - edge: edge(), - edgeHeight: 20, - edgeOpacity: 0.95, - edgeIdle: 100, - edgeFade: 0.6, - edgeRise: 0.1, - grow: true, - watch: watch(), - animateToggle: true, - open: visible(), - toggleSpring: contextSpring(), - }) - return ( - -
- - {(entry) => ( - <> - - groupState.write(entry().groupKey, value)} - /> - - - - - )} - - - {(value) => ( - - )} - - - {(entry) => ( - - -
- -
-
+ return ( + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() + if (entry.type !== "context") return emptyTools + return entry.refs + .map((ref) => partByID(list(data.store.part?.[ref.messageID], emptyParts), ref.partID)) + .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) + const busy = createMemo(() => props.working && last() === entryAccessor().key) + + return ( + 0}> + + + ) + })()} + + + {(() => { + const message = createMemo(() => { + const entry = entryAccessor() + if (entry.type !== "part") return + return props.messages.find((item) => item.id === entry.ref.messageID) + }) + const part = createMemo(() => { + const entry = entryAccessor() + if (entry.type !== "part") return + return partByID(list(data.store.part?.[entry.ref.messageID], emptyParts), entry.ref.partID) + }) + + return ( + + + - )} - -
-
- ) - }} -
-
+ + ) + })()} + + + ) + }} + ) } @@ -618,6 +560,76 @@ function isContextGroupTool(part: PartType): part is ToolPart { return part.type === "tool" && CONTEXT_GROUP_TOOLS.has(part.tool) } +function contextToolDetail(part: ToolPart): string | undefined { + const info = getToolInfo(part.tool, part.state.input ?? {}) + if (info.subtitle) return info.subtitle + if (part.state.status === "error") return part.state.error + if ((part.state.status === "running" || part.state.status === "completed") && part.state.title) + return part.state.title + const description = part.state.input?.description + if (typeof description === "string") return description + return undefined +} + +function contextToolTrigger(part: ToolPart, i18n: ReturnType) { + const input = (part.state.input ?? {}) as Record + const path = typeof input.path === "string" ? input.path : "/" + const filePath = typeof input.filePath === "string" ? input.filePath : undefined + const pattern = typeof input.pattern === "string" ? input.pattern : undefined + const include = typeof input.include === "string" ? input.include : undefined + const offset = typeof input.offset === "number" ? input.offset : undefined + const limit = typeof input.limit === "number" ? input.limit : undefined + + switch (part.tool) { + case "read": { + const args: string[] = [] + if (offset !== undefined) args.push("offset=" + offset) + if (limit !== undefined) args.push("limit=" + limit) + return { + title: i18n.t("ui.tool.read"), + subtitle: filePath ? getFilename(filePath) : "", + args, + } + } + case "list": + return { + title: i18n.t("ui.tool.list"), + subtitle: getDirectory(path), + } + case "glob": + return { + title: i18n.t("ui.tool.glob"), + subtitle: getDirectory(path), + args: pattern ? ["pattern=" + pattern] : [], + } + case "grep": { + const args: string[] = [] + if (pattern) args.push("pattern=" + pattern) + if (include) args.push("include=" + include) + return { + title: i18n.t("ui.tool.grep"), + subtitle: getDirectory(path), + args, + } + } + default: { + const info = getToolInfo(part.tool, input) + return { + title: info.title, + subtitle: info.subtitle || contextToolDetail(part), + args: [], + } + } + } +} + +function contextToolSummary(parts: ToolPart[]) { + const read = parts.filter((part) => part.tool === "read").length + const search = parts.filter((part) => part.tool === "glob" || part.tool === "grep").length + const list = parts.filter((part) => part.tool === "list").length + return { read, search, list } +} + function ExaOutput(props: { output?: string }) { const links = createMemo(() => urls(props.output)) @@ -648,11 +660,210 @@ export function registerPartComponent(type: string, component: PartComponent) { PART_MAPPING[type] = component } +export function Message(props: MessageProps) { + return ( + + + {(userMessage) => ( + + )} + + + {(assistantMessage) => ( + + )} + + + ) +} + +export function AssistantMessageDisplay(props: { + message: AssistantMessage + parts: PartType[] + showAssistantCopyPartID?: string | null + showReasoningSummaries?: boolean +}) { + const emptyTools: ToolPart[] = [] + const grouped = createMemo( + () => + groupParts( + props.parts + .filter((part) => renderable(part, props.showReasoningSummaries ?? true)) + .map((part) => ({ + messageID: props.message.id, + part, + })), + ), + [] as PartGroup[], + { equals: sameGroups }, + ) + + return ( + + {(entryAccessor) => { + const entryType = createMemo(() => entryAccessor().type) + + return ( + + + {(() => { + const parts = createMemo( + () => { + const entry = entryAccessor() + if (entry.type !== "context") return emptyTools + return entry.refs + .map((ref) => partByID(props.parts, ref.partID)) + .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) + }, + emptyTools, + { equals: same }, + ) + + return ( + 0}> + + + ) + })()} + + + {(() => { + const part = createMemo(() => { + const entry = entryAccessor() + if (entry.type !== "part") return + return partByID(props.parts, entry.ref.partID) + }) + + return ( + + + + ) + })()} + + + ) + }} + + ) +} + +function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { + const i18n = useI18n() + const [open, setOpen] = createSignal(false) + const pending = createMemo( + () => + !!props.busy || props.parts.some((part) => part.state.status === "pending" || part.state.status === "running"), + ) + const summary = createMemo(() => contextToolSummary(props.parts)) + + return ( + + +
+ + + + + + + + + +
+
+ +
+ + {(partAccessor) => { + const trigger = createMemo(() => contextToolTrigger(partAccessor(), i18n)) + const running = createMemo( + () => partAccessor().state.status === "pending" || partAccessor().state.status === "running", + ) + return ( +
+
+
+
+
+
+ + + + + {trigger().subtitle} + + + + {(arg) => {arg}} + + +
+
+
+
+
+
+ ) + }} +
+
+
+
+ ) +} + export function UserMessageDisplay(props: { message: UserMessage parts: PartType[] interrupted?: boolean - animate?: boolean queued?: boolean }) { const data = useData() @@ -702,9 +913,14 @@ export function UserMessageDisplay(props: { return `${hour12}:${minute} ${hours < 12 ? "AM" : "PM"}` }) - const userMeta = createMemo(() => { + const metaHead = createMemo(() => { const agent = props.message.agent - const items = [agent ? agent[0]?.toUpperCase() + agent.slice(1) : "", model(), stamp()] + const items = [agent ? agent[0]?.toUpperCase() + agent.slice(1) : "", model()] + return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0") + }) + + const metaTail = createMemo(() => { + const items = [stamp(), props.interrupted ? i18n.t("ui.message.interrupted") : ""] return items.filter((x) => !!x).join("\u00A0\u00B7\u00A0") }) @@ -721,83 +937,93 @@ export function UserMessageDisplay(props: { } return ( - -
-
- 0}> -
- - {(file) => ( -
{ - if (file.mime.startsWith("image/") && file.url) { - openImagePreview(file.url, file.filename) - } - }} - > - - -
- } - > - {file.filename - -
- )} - -
- - - <> -
-
- -
- -
- -
-
+
+ 0}> +
+ + {(file) => ( +
{ + if (file.mime.startsWith("image/") && file.url) { + openImagePreview(file.url, file.filename) + } + }} + > + + +
+ } + > + {file.filename +
-
- + )} + +
+
+ + <> +
+
+ +
+ +
+ +
+
+
+
+ + + - {userMeta()} + {metaHead()} - - e.preventDefault()} - onClick={(event) => { - event.stopPropagation() - handleCopy() - }} - aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")} - /> - -
- -
-
-
- + + + {"\u00A0\u00B7\u00A0"} + + + + + {metaTail()} + + + +
+ + e.preventDefault()} + onClick={(event) => { + event.stopPropagation() + handleCopy() + }} + aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")} + /> + +
+ + +
) } @@ -851,10 +1077,7 @@ export function Part(props: MessagePartProps) { hideDetails={props.hideDetails} defaultOpen={props.defaultOpen} showAssistantCopyPartID={props.showAssistantCopyPartID} - showTurnDiffSummary={props.showTurnDiffSummary} - turnDiffSummary={props.turnDiffSummary} - animate={props.animate} - working={props.working} + turnDurationMs={props.turnDurationMs} /> ) @@ -864,16 +1087,12 @@ export interface ToolProps { input: Record metadata: Record tool: string - partID?: string - callID?: string output?: string status?: string hideDetails?: boolean defaultOpen?: boolean forceOpen?: boolean locked?: boolean - animate?: boolean - reveal?: boolean } export type ToolComponent = Component @@ -907,7 +1126,7 @@ function ToolFileAccordion(props: { path: string; actions?: JSX.Element; childre @@ -938,26 +1157,30 @@ function ToolFileAccordion(props: { path: string; actions?: JSX.Element; childre PART_MAPPING["tool"] = function ToolPartDisplay(props) { const i18n = useI18n() - const part = props.part as ToolPart - const hideQuestion = createMemo(() => part.tool === "question" && busy(part.state.status)) + const part = () => props.part as ToolPart + if (part().tool === "todowrite" || part().tool === "todoread") return null + + const hideQuestion = createMemo( + () => part().tool === "question" && (part().state.status === "pending" || part().state.status === "running"), + ) const emptyInput: Record = {} const emptyMetadata: Record = {} - const input = () => part.state?.input ?? emptyInput + const input = () => part().state?.input ?? emptyInput // @ts-expect-error - const partMetadata = () => part.state?.metadata ?? emptyMetadata + const partMetadata = () => part().state?.metadata ?? emptyMetadata - const render = createMemo(() => ToolRegistry.render(part.tool) ?? GenericTool) + const render = createMemo(() => ToolRegistry.render(part().tool) ?? GenericTool) return ( -
+
- + {(error) => { const cleaned = error().replace("Error: ", "") - if (part.tool === "question" && cleaned.includes("dismissed this question")) { + if (part().tool === "question" && cleaned.includes("dismissed this question")) { return (
@@ -991,17 +1214,13 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { @@ -1026,16 +1245,74 @@ PART_MAPPING["compaction"] = function CompactionPartDisplay() { } PART_MAPPING["text"] = function TextPartDisplay(props) { + const data = useData() + const i18n = useI18n() const part = () => props.part as TextPart + const interrupted = createMemo( + () => + props.message.role === "assistant" && (props.message as AssistantMessage).error?.name === "MessageAbortedError", + ) + + const model = createMemo(() => { + if (props.message.role !== "assistant") return "" + const message = props.message as AssistantMessage + const match = data.store.provider?.all?.find((p) => p.id === message.providerID) + return match?.models?.[message.modelID]?.name ?? message.modelID + }) + + const duration = createMemo(() => { + if (props.message.role !== "assistant") return "" + const message = props.message as AssistantMessage + const completed = message.time.completed + const ms = + typeof props.turnDurationMs === "number" + ? props.turnDurationMs + : typeof completed === "number" + ? completed - message.time.created + : -1 + if (!(ms >= 0)) return "" + const total = Math.round(ms / 1000) + if (total < 60) return `${total}s` + const minutes = Math.floor(total / 60) + const seconds = total % 60 + return `${minutes}m ${seconds}s` + }) + + const meta = createMemo(() => { + if (props.message.role !== "assistant") return "" + const agent = (props.message as AssistantMessage).agent + const items = [ + agent ? agent[0]?.toUpperCase() + agent.slice(1) : "", + model(), + duration(), + interrupted() ? i18n.t("ui.message.interrupted") : "", + ] + return items.filter((x) => !!x).join(" \u00B7 ") + }) const displayText = () => (part().text ?? "").trim() const throttledText = createThrottledValue(displayText) - const summary = createMemo(() => { - if (props.message.role !== "assistant") return - if (!props.showTurnDiffSummary) return - if (props.showAssistantCopyPartID !== part().id) return - return props.turnDiffSummary + const isLastTextPart = createMemo(() => { + const last = (data.store.part?.[props.message.id] ?? []) + .filter((item): item is TextPart => item?.type === "text" && !!item.text?.trim()) + .at(-1) + return last?.id === part().id }) + const showCopy = createMemo(() => { + if (props.message.role !== "assistant") return isLastTextPart() + if (props.showAssistantCopyPartID === null) return false + if (typeof props.showAssistantCopyPartID === "string") return props.showAssistantCopyPartID === part().id + return isLastTextPart() + }) + const [copied, setCopied] = createSignal(false) + + const handleCopy = async () => { + const content = displayText() + if (!content) return + await navigator.clipboard.writeText(content) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } return ( @@ -1043,12 +1320,28 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
- - {(render) => ( - -
{render()()}
-
- )} + +
+ + e.preventDefault()} + onClick={handleCopy} + aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")} + /> + + + + {meta()} + + +
@@ -1078,33 +1371,30 @@ ToolRegistry.register({ if (props.input.offset) args.push("offset=" + props.input.offset) if (props.input.limit) args.push("limit=" + props.input.limit) const loaded = createMemo(() => { + if (props.status !== "completed") return [] const value = props.metadata.loaded if (!value || !Array.isArray(value)) return [] return value.filter((p): p is string => typeof p === "string") }) - const pending = createMemo(() => busy(props.status)) return ( <> - - } + trigger={{ + title: i18n.t("ui.tool.read"), + subtitle: props.input.filePath ? getFilename(props.input.filePath) : "", + args, + }} /> {(filepath) => ( - +
+ + + {i18n.t("ui.tool.loaded")} {relativizeProjectPath(filepath, data.directory)} + +
)}
@@ -1116,29 +1406,18 @@ ToolRegistry.register({ name: "list", render(props) { const i18n = useI18n() - const pending = createMemo(() => busy(props.status)) return ( - - } + trigger={{ title: i18n.t("ui.tool.list"), subtitle: getDirectory(props.input.path || "/") }} > - {(output) => ( -
- -
- )} +
+ +
-
+ ) }, }) @@ -1147,30 +1426,22 @@ ToolRegistry.register({ name: "glob", render(props) { const i18n = useI18n() - const pending = createMemo(() => busy(props.status)) return ( - - -
- )} +
+ +
- + ) }, }) @@ -1182,214 +1453,40 @@ ToolRegistry.register({ const args: string[] = [] if (props.input.pattern) args.push("pattern=" + props.input.pattern) if (props.input.include) args.push("include=" + props.input.include) - const pending = createMemo(() => busy(props.status)) return ( - - } + trigger={{ + title: i18n.t("ui.tool.grep"), + subtitle: getDirectory(props.input.path || "/"), + args, + }} > - {(output) => ( -
- -
- )} +
+ +
-
+ ) }, }) -function useToolReveal(pending: () => boolean, animate?: () => boolean) { - const enabled = () => animate?.() ?? true - const [live, setLive] = createSignal(pending() || enabled()) - createEffect(() => { - if (pending()) setLive(true) - }) - return () => enabled() && live() -} - -function WebfetchMeta(props: { url: string; animate?: boolean }) { - let ref: HTMLSpanElement | undefined - useToolFade(() => ref, { wipe: true, animate: props.animate }) - - return ( - - event.stopPropagation()} - > - {props.url} - -
- -
-
- ) -} - -function TaskLink(props: { href: string; text: string; onClick: (e: MouseEvent) => void; animate?: boolean }) { - let ref: HTMLAnchorElement | undefined - useToolFade(() => ref, { wipe: true, animate: props.animate }) - - return ( - - {props.text} - - ) -} - -function ToolText(props: { text: string; delay?: number; animate?: boolean }) { - let ref: HTMLSpanElement | undefined - useToolFade(() => ref, { delay: props.delay, wipe: true, animate: props.animate }) - - return ( - - {props.text} - - ) -} - -function ToolLoadedFile(props: { text: string; animate?: boolean }) { - let ref: HTMLDivElement | undefined - useToolFade(() => ref, { delay: 0.02, wipe: true, animate: props.animate }) - - return ( - -
- - {props.text} -
-
- ) -} - -function ToolTriggerRow(props: { - title: string - pending: boolean - subtitle?: string - args?: string[] - action?: JSX.Element - animate?: boolean - revealOnMount?: boolean -}) { - const reveal = useToolReveal( - () => props.pending, - () => props.animate !== false, - ) - const detail = createMemo(() => [props.subtitle, ...(props.args ?? [])].filter((x): x is string => !!x).join(" ")) - const detailAnimate = createMemo(() => { - if (props.animate === false) return false - if (props.revealOnMount) return true - if (!props.pending && !reveal()) return true - return reveal() - }) - - return ( -
-
- - - - {(text) => } -
- {props.action} -
- ) -} - -type DiffValue = { additions: number; deletions: number } | { additions: number; deletions: number }[] - -function ToolMetaLine(props: { - filename: string - path?: string - changes?: DiffValue - delay?: number - animate?: boolean - soft?: boolean -}) { - let ref: HTMLSpanElement | undefined - useToolFade(() => ref, { delay: props.delay ?? 0.02, wipe: true, animate: props.animate }) - - return ( - - {props.filename} - - {props.path} - - {(changes) => } - - ) -} - -function ToolChanges(props: { changes: DiffValue; animate?: boolean }) { - let ref: HTMLDivElement | undefined - useToolFade(() => ref, { delay: 0.04, animate: props.animate }) - - return ( -
- -
- ) -} - -function ShellText(props: { text: string; animate?: boolean }) { - let ref: HTMLSpanElement | undefined - useToolFade(() => ref, { wipe: true, animate: props.animate }) - - return ( - - - - {props.text} - - - - ) -} - ToolRegistry.register({ name: "webfetch", render(props) { const i18n = useI18n() - const pending = createMemo(() => busy(props.status)) - const reveal = useToolReveal(pending, () => props.reveal !== false) + const pending = createMemo(() => props.status === "pending" || props.status === "running") const url = createMemo(() => { const value = props.input.url if (typeof value !== "string") return "" return value }) return ( - @@ -1397,8 +1494,24 @@ ToolRegistry.register({ - {(value) => } + + event.stopPropagation()} + > + {url()} + +
+ +
+ +
+
} /> @@ -1417,8 +1530,7 @@ ToolRegistry.register({ }) return ( - - + ) }, }) @@ -1444,8 +1556,7 @@ ToolRegistry.register({ }) return ( - - + ) }, }) @@ -1465,6 +1576,7 @@ ToolRegistry.register({ render(props) { const data = useData() const i18n = useI18n() + const location = useLocation() const childSessionId = () => props.metadata.sessionId as string | undefined const type = createMemo(() => { const raw = props.input.subagent_type @@ -1477,8 +1589,7 @@ ToolRegistry.register({ if (typeof value === "string") return value return undefined }) - const running = createMemo(() => busy(props.status)) - const reveal = useToolReveal(running, () => props.reveal !== false) + const running = createMemo(() => props.status === "pending" || props.status === "running") const href = createMemo(() => { const sessionId = childSessionId() @@ -1487,49 +1598,34 @@ 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}` }) - const handleLinkClick = (e: MouseEvent) => { - const sessionId = childSessionId() - const url = href() - if (!sessionId || !url) return - - e.stopPropagation() - - if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return - - const nav = data.navigateToSession - if (!nav || typeof window === "undefined") return - - e.preventDefault() - const before = window.location.pathname + window.location.search + window.location.hash - nav(sessionId) - setTimeout(() => { - const after = window.location.pathname + window.location.search + window.location.hash - if (after === before) window.location.assign(url) - }, 50) - } + const titleContent = () => const trigger = () => (
- - + + {titleContent()} - {(url) => ( - - )} + e.stopPropagation()} + > + {description()} + - + {description()} @@ -1537,7 +1633,7 @@ ToolRegistry.register({
) - return + return }, }) @@ -1545,26 +1641,13 @@ ToolRegistry.register({ name: "bash", render(props) { const i18n = useI18n() - const pending = () => busy(props.status) - const reveal = useToolReveal(pending, () => props.reveal !== false) - const subtitle = () => props.input.description ?? props.metadata.description - const cmd = createMemo(() => { - const value = props.input.command ?? props.metadata.command - if (typeof value === "string") return value - return "" - }) - const output = createMemo(() => { - if (typeof props.output === "string") return props.output - if (typeof props.metadata.output === "string") return props.metadata.output - return "" - }) - const command = createMemo(() => `$ ${cmd()}`) - const result = createMemo(() => stripAnsi(output())) + const pending = () => props.status === "pending" || props.status === "running" + const sawPending = pending() const text = createMemo(() => { - const value = result() - return `${command()}${value ? "\n\n" + value : ""}` + const cmd = props.input.command ?? props.metadata.command ?? "" + const out = stripAnsi(props.output || props.metadata.output || "") + return `$ ${cmd}${out ? "\n\n" + out : ""}` }) - const hasOutput = createMemo(() => result().length > 0) const [copied, setCopied] = createSignal(false) const handleCopy = async () => { @@ -1576,20 +1659,18 @@ ToolRegistry.register({ } return ( -
- {(text) => } + + +
} @@ -1617,7 +1698,7 @@ ToolRegistry.register({ - + ) }, }) @@ -1630,12 +1711,10 @@ ToolRegistry.register({ const diagnostics = createMemo(() => getDiagnostics(props.metadata.diagnostics, props.input.filePath)) const path = createMemo(() => props.metadata?.filediff?.file || props.input.filePath || "") const filename = () => getFilename(props.input.filePath ?? "") - const pending = () => busy(props.status) - const reveal = useToolReveal(pending, () => props.reveal !== false) + const pending = () => props.status === "pending" || props.status === "running" return (
- - - {(name) => ( - - )} + + {filename()}
+ +
+ {getDirectory(props.input.filePath!)} +
+
+ +
+ + +
} @@ -1666,7 +1748,7 @@ ToolRegistry.register({ path={path()} actions={ - {(diff) => } + } > @@ -1687,7 +1769,7 @@ ToolRegistry.register({ - + ) }, @@ -1701,12 +1783,10 @@ ToolRegistry.register({ const diagnostics = createMemo(() => getDiagnostics(props.metadata.diagnostics, props.input.filePath)) const path = createMemo(() => props.input.filePath || "") const filename = () => getFilename(props.input.filePath ?? "") - const pending = () => busy(props.status) - const reveal = useToolReveal(pending, () => props.reveal !== false) + const pending = () => props.status === "pending" || props.status === "running" return (
- - - {(name) => ( - - )} + + {filename()}
+ +
+ {getDirectory(props.input.filePath!)} +
+
+
{/* */}
} > @@ -1748,7 +1828,7 @@ ToolRegistry.register({ - + ) }, @@ -1772,8 +1852,7 @@ ToolRegistry.register({ const i18n = useI18n() const fileComponent = useFileComponent() const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[]) - const pending = createMemo(() => busy(props.status)) - const reveal = useToolReveal(pending, () => props.reveal !== false) + const pending = createMemo(() => props.status === "pending" || props.status === "running") const single = createMemo(() => { const list = files() if (list.length !== 1) return @@ -1781,6 +1860,7 @@ ToolRegistry.register({ }) const [expanded, setExpanded] = createSignal([]) let seeded = false + createEffect(() => { const list = files() if (list.length === 0) return @@ -1788,6 +1868,7 @@ ToolRegistry.register({ seeded = true setExpanded(list.filter((f) => f.type !== "delete").map((f) => f.filePath)) }) + const subtitle = createMemo(() => { const count = files().length if (count === 0) return "" @@ -1795,44 +1876,24 @@ ToolRegistry.register({ }) return ( -
- -
-
- - - - - {(file) => ( - - )} - - {(text) => } -
-
-
- } - > - + 0}> setExpanded(Array.isArray(value) ? value : value ? [value] : [])} > @@ -1840,11 +1901,13 @@ ToolRegistry.register({ {(file) => { const active = createMemo(() => expanded().includes(file.filePath)) const [visible, setVisible] = createSignal(false) + createEffect(() => { if (!active()) { setVisible(false) return } + requestAnimationFrame(() => { if (!active()) return setVisible(true) @@ -1909,50 +1972,77 @@ ToolRegistry.register({ + + + } + > +
+ +
+
+ + + + + {getFilename(single()!.relativePath)} + +
+ +
+ {getDirectory(single()!.relativePath)} +
+
+
+
+ + + +
+
} > - {(file) => ( - - - - {i18n.t("ui.patch.action.created")} - - - - - {i18n.t("ui.patch.action.deleted")} - - - - - {i18n.t("ui.patch.action.moved")} - - - - - - - } - > -
- -
-
- )} -
- - + + + + {i18n.t("ui.patch.action.created")} + + + + + {i18n.t("ui.patch.action.deleted")} + + + + + {i18n.t("ui.patch.action.moved")} + + + + + + + } + > +
+ +
+
+ + + ) }, }) @@ -1970,7 +2060,6 @@ ToolRegistry.register({ return [] }) - const pending = createMemo(() => busy(props.status)) const subtitle = createMemo(() => { const list = todos() @@ -1979,19 +2068,14 @@ ToolRegistry.register({ }) return ( - - } + trigger={{ + title: i18n.t("ui.tool.todos"), + subtitle: subtitle(), + }} >
@@ -2009,7 +2093,7 @@ ToolRegistry.register({
-
+ ) }, }) @@ -2021,7 +2105,6 @@ ToolRegistry.register({ const questions = createMemo(() => (props.input.questions ?? []) as QuestionInfo[]) const answers = createMemo(() => (props.metadata.answers ?? []) as QuestionAnswer[]) const completed = createMemo(() => answers().length > 0) - const pending = createMemo(() => busy(props.status)) const subtitle = createMemo(() => { const count = questions().length @@ -2031,19 +2114,14 @@ ToolRegistry.register({ }) return ( - - } + trigger={{ + title: i18n.t("ui.tool.questions"), + subtitle: subtitle(), + }} >
@@ -2060,7 +2138,7 @@ ToolRegistry.register({
-
+ ) }, }) @@ -2068,28 +2146,21 @@ ToolRegistry.register({ ToolRegistry.register({ name: "skill", render(props) { - const i18n = useI18n() - const pending = createMemo(() => busy(props.status)) - const name = createMemo(() => { - const value = props.input.name || props.metadata.name - if (typeof value === "string") return value - }) - return ( - - } - animate - /> + const title = createMemo(() => props.input.name || "skill") + const running = createMemo(() => props.status === "pending" || props.status === "running") + + const titleContent = () => + + const trigger = () => ( +
+
+ + {titleContent()} + +
+
) + + return }, }) diff --git a/packages/ui/src/components/motion-spring.tsx b/packages/ui/src/components/motion-spring.tsx index c7ff1fbcd2..a5104a1a3e 100644 --- a/packages/ui/src/components/motion-spring.tsx +++ b/packages/ui/src/components/motion-spring.tsx @@ -1,9 +1,8 @@ import { attachSpring, motionValue } from "motion" import type { SpringOptions } from "motion" import { createEffect, createSignal, onCleanup } from "solid-js" -import { useReducedMotion } from "../hooks/use-reduced-motion" -type Opt = Pick +type Opt = Partial> const eq = (a: Opt | undefined, b: Opt | undefined) => a?.visualDuration === b?.visualDuration && a?.bounce === b?.bounce && @@ -14,41 +13,24 @@ const eq = (a: Opt | undefined, b: Opt | undefined) => export function useSpring(target: () => number, options?: Opt | (() => Opt)) { const read = () => (typeof options === "function" ? options() : options) - const reduce = useReducedMotion() const [value, setValue] = createSignal(target()) const source = motionValue(value()) const spring = motionValue(value()) let config = read() - let reduced = reduce() - let stop = reduced ? () => {} : attachSpring(spring, source, config) - let off = spring.on("change", (next) => setValue(next)) + let stop = attachSpring(spring, source, config) + let off = spring.on("change", (next: number) => setValue(next)) createEffect(() => { - const next = target() - if (reduced) { - source.set(next) - spring.set(next) - setValue(next) - return - } - source.set(next) + source.set(target()) }) createEffect(() => { + if (!options) return const next = read() - const skip = reduce() - if (eq(config, next) && reduced === skip) return + if (eq(config, next)) return config = next - reduced = skip stop() - stop = skip ? () => {} : attachSpring(spring, source, next) - if (skip) { - const value = target() - source.set(value) - spring.set(value) - setValue(value) - return - } + stop = attachSpring(spring, source, next) setValue(spring.get()) }) diff --git a/packages/ui/src/components/motion.tsx b/packages/ui/src/components/motion.tsx deleted file mode 100644 index 6cdf01c731..0000000000 --- a/packages/ui/src/components/motion.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { followValue } from "motion" -import type { MotionValue } from "motion" - -export { animate, springValue } from "motion" -export type { AnimationPlaybackControls } from "motion" - -/** - * Like `springValue` but preserves getters on the config object. - * `springValue` spreads config at creation, snapshotting getter values. - * This passes the config through to `followValue` intact, so getters - * on `visualDuration` etc. fire on every `.set()` call. - */ -export function tunableSpringValue(initial: T, config: SpringConfig): MotionValue { - return followValue(initial, config as any) -} - -let _growDuration = 0.5 -let _collapsibleDuration = 0.3 - -export const GROW_SPRING = { - type: "spring" as const, - get visualDuration() { - return _growDuration - }, - bounce: 0, -} - -export const COLLAPSIBLE_SPRING = { - type: "spring" as const, - get visualDuration() { - return _collapsibleDuration - }, - bounce: 0, -} - -export const setGrowDuration = (v: number) => { - _growDuration = v -} -export const setCollapsibleDuration = (v: number) => { - _collapsibleDuration = v -} -export const getGrowDuration = () => _growDuration -export const getCollapsibleDuration = () => _collapsibleDuration - -export type SpringConfig = { type: "spring"; visualDuration: number; bounce: number } - -export const FAST_SPRING = { - type: "spring" as const, - visualDuration: 0.35, - bounce: 0, -} - -export const GLOW_SPRING = { - type: "spring" as const, - visualDuration: 0.4, - bounce: 0.15, -} - -export const WIPE_MASK = - "linear-gradient(to right, rgba(0,0,0,1) 0%, rgba(0,0,0,1) 45%, rgba(0,0,0,0) 60%, rgba(0,0,0,0) 100%)" - -export const clearMaskStyles = (el: HTMLElement) => { - el.style.maskImage = "" - el.style.webkitMaskImage = "" - el.style.maskSize = "" - el.style.webkitMaskSize = "" - el.style.maskRepeat = "" - el.style.webkitMaskRepeat = "" - el.style.maskPosition = "" - el.style.webkitMaskPosition = "" -} - -export const clearFadeStyles = (el: HTMLElement) => { - el.style.opacity = "" - el.style.filter = "" - el.style.transform = "" -} diff --git a/packages/ui/src/components/rolling-results.css b/packages/ui/src/components/rolling-results.css deleted file mode 100644 index 200b2a97e9..0000000000 --- a/packages/ui/src/components/rolling-results.css +++ /dev/null @@ -1,92 +0,0 @@ -[data-component="rolling-results"] { - --rolling-results-row-height: 22px; - --rolling-results-fixed-height: var(--rolling-results-row-height); - --rolling-results-fixed-gap: 0px; - --rolling-results-row-gap: 0px; - - display: block; - width: 100%; - min-width: 0; - - [data-slot="rolling-results-viewport"] { - position: relative; - min-width: 0; - height: 0; - overflow: clip; - } - - &[data-overflowing="true"]:not([data-scrollable="true"]) [data-slot="rolling-results-window"] { - mask-image: linear-gradient( - to bottom, - transparent 0%, - black var(--rolling-results-fade), - black calc(100% - calc(var(--rolling-results-fade) * 0.5)), - transparent 100% - ); - -webkit-mask-image: linear-gradient( - to bottom, - transparent 0%, - black var(--rolling-results-fade), - black calc(100% - calc(var(--rolling-results-fade) * 0.5)), - transparent 100% - ); - } - - [data-slot="rolling-results-fixed"] { - min-width: 0; - height: var(--rolling-results-fixed-height); - min-height: var(--rolling-results-fixed-height); - display: flex; - align-items: center; - } - - [data-slot="rolling-results-window"] { - min-width: 0; - margin-top: var(--rolling-results-fixed-gap); - height: calc(100% - var(--rolling-results-fixed-height) - var(--rolling-results-fixed-gap)); - overflow: clip; - } - - &[data-scrollable="true"] [data-slot="rolling-results-window"] { - scrollbar-width: none; - -ms-overflow-style: none; - - &::-webkit-scrollbar { - display: none; - } - } - - &[data-scrollable="true"] [data-slot="rolling-results-track"] { - transform: none !important; - will-change: auto; - } - - [data-slot="rolling-results-body"] { - min-width: 0; - } - - [data-slot="rolling-results-track"] { - display: flex; - min-width: 0; - flex-direction: column; - gap: var(--rolling-results-row-gap); - will-change: transform; - } - - [data-slot="rolling-results-row"], - [data-slot="rolling-results-empty"] { - min-width: 0; - height: var(--rolling-results-row-height); - min-height: var(--rolling-results-row-height); - display: flex; - align-items: center; - } - - [data-slot="rolling-results-row"] { - color: var(--text-base); - } - - [data-slot="rolling-results-empty"] { - color: var(--text-weaker); - } -} diff --git a/packages/ui/src/components/rolling-results.tsx b/packages/ui/src/components/rolling-results.tsx deleted file mode 100644 index 77ffdb1b34..0000000000 --- a/packages/ui/src/components/rolling-results.tsx +++ /dev/null @@ -1,325 +0,0 @@ -import { For, Show, batch, createEffect, createMemo, createSignal, on, onCleanup, onMount, type JSX } from "solid-js" -import { useReducedMotion } from "../hooks/use-reduced-motion" -import { animate, clearMaskStyles, GROW_SPRING, type AnimationPlaybackControls, type SpringConfig } from "./motion" - -export type RollingResultsProps = { - items: T[] - render: (item: T, index: number) => JSX.Element - fixed?: JSX.Element - getKey?: (item: T, index: number) => string - rows?: number - rowHeight?: number - fixedHeight?: number - rowGap?: number - open?: boolean - scrollable?: boolean - spring?: SpringConfig - animate?: boolean - class?: string - empty?: JSX.Element - noFadeOnCollapse?: boolean -} - -export function RollingResults(props: RollingResultsProps) { - let view: HTMLDivElement | undefined - let track: HTMLDivElement | undefined - let windowEl: HTMLDivElement | undefined - let shift: AnimationPlaybackControls | undefined - let resize: AnimationPlaybackControls | undefined - let edgeFade: AnimationPlaybackControls | undefined - const reduce = useReducedMotion() - - const rows = createMemo(() => Math.max(1, Math.round(props.rows ?? 3))) - const rowHeight = createMemo(() => Math.max(16, Math.round(props.rowHeight ?? 22))) - const fixedHeight = createMemo(() => Math.max(0, Math.round(props.fixedHeight ?? rowHeight()))) - const rowGap = createMemo(() => Math.max(0, Math.round(props.rowGap ?? 0))) - const fixed = createMemo(() => props.fixed !== undefined) - const list = createMemo(() => props.items ?? []) - const count = createMemo(() => list().length) - - // scrollReady is the internal "transition complete" state. - // It only becomes true after props.scrollable is true AND the offset animation has settled. - const [scrollReady, setScrollReady] = createSignal(false) - - const backstop = createMemo(() => Math.max(rows() * 2, 12)) - const rendered = createMemo(() => { - const items = list() - if (scrollReady()) return items - const max = backstop() - return items.length > max ? items.slice(-max) : items - }) - const skipped = createMemo(() => { - if (scrollReady()) return 0 - return count() - rendered().length - }) - const open = createMemo(() => props.open !== false) - const active = createMemo(() => (props.animate !== false || props.spring !== undefined) && !reduce()) - const noFade = () => props.noFadeOnCollapse === true - const overflowing = createMemo(() => count() > rows()) - const shown = createMemo(() => Math.min(rows(), count())) - const step = createMemo(() => rowHeight() + rowGap()) - const offset = createMemo(() => Math.max(0, count() - shown()) * step()) - const body = createMemo(() => { - if (shown() > 0) { - return shown() * rowHeight() + Math.max(0, shown() - 1) * rowGap() - } - if (props.empty === undefined) return 0 - return rowHeight() - }) - const gap = createMemo(() => { - if (!fixed()) return 0 - if (body() <= 0) return 0 - return rowGap() - }) - const height = createMemo(() => { - if (!open()) return 0 - if (!fixed()) return body() - return fixedHeight() + gap() + body() - }) - - const key = (item: T, index: number) => { - const value = props.getKey - if (value) return value(item, index) - return String(index) - } - - const setTrack = (value: number) => { - if (!track) return - track.style.transform = `translateY(${-Math.round(value)}px)` - } - - const setView = (value: number) => { - if (!view) return - view.style.height = `${Math.max(0, Math.round(value))}px` - } - - onMount(() => { - setTrack(offset()) - }) - - // Original WAAPI offset animation — untouched rolling behavior. - createEffect( - on( - offset, - (next) => { - if (!track) return - if (scrollReady()) return - if (props.scrollable) return - if (!active()) { - shift?.stop() - shift = undefined - setTrack(next) - return - } - shift?.stop() - const anim = animate(track, { transform: `translateY(${-next}px)` }, props.spring ?? GROW_SPRING) - shift = anim - anim.finished - .catch(() => {}) - .finally(() => { - if (shift !== anim) return - setTrack(next) - shift = undefined - }) - }, - { defer: true }, - ), - ) - - // Scrollable transition: wait for the offset animation to finish, - // then batch all DOM changes in one synchronous pass. - createEffect( - on( - () => props.scrollable === true, - (isScrollable) => { - if (!isScrollable) { - setScrollReady(false) - if (windowEl) { - windowEl.style.overflowY = "" - windowEl.style.maskImage = "" - windowEl.style.webkitMaskImage = "" - } - return - } - // Wait for the current offset animation to settle (if any). - const done = shift?.finished ?? Promise.resolve() - done - .catch(() => {}) - .then(() => { - if (props.scrollable !== true) return - - // Batch the signal update — Solid updates the DOM synchronously: - // rendered() returns all items, skipped() returns 0, padding-top removed, - // data-scrollable becomes "true". - batch(() => setScrollReady(true)) - - // Now the DOM has all items. Safe to switch layout strategy. - // CSS handles `transform: none !important` on [data-scrollable="true"]. - if (windowEl) { - windowEl.style.overflowY = "auto" - windowEl.scrollTop = windowEl.scrollHeight - } - updateScrollMask() - }) - }, - ), - ) - - // Auto-scroll to bottom when new items arrive in scrollable mode - const [userScrolled, setUserScrolled] = createSignal(false) - - const updateScrollMask = () => { - if (!windowEl) return - if (!scrollReady()) { - windowEl.style.maskImage = "" - windowEl.style.webkitMaskImage = "" - return - } - const { scrollTop, scrollHeight, clientHeight } = windowEl - const atBottom = scrollHeight - scrollTop - clientHeight < 8 - // Top fade is always present in scrollable mode (matches rolling mode appearance). - // Bottom fade only when not scrolled to the end. - const mask = atBottom - ? "linear-gradient(to bottom, transparent 0, black 8px)" - : "linear-gradient(to bottom, transparent 0, black 8px, black calc(100% - 8px), transparent 100%)" - windowEl.style.maskImage = mask - windowEl.style.webkitMaskImage = mask - } - - createEffect(() => { - if (!scrollReady()) { - setUserScrolled(false) - return - } - const _n = count() - const scrolled = userScrolled() - if (scrolled) return - if (windowEl) { - windowEl.scrollTop = windowEl.scrollHeight - updateScrollMask() - } - }) - - const onWindowScroll = () => { - if (!windowEl || !scrollReady()) return - const atBottom = windowEl.scrollHeight - windowEl.scrollTop - windowEl.clientHeight < 8 - setUserScrolled(!atBottom) - updateScrollMask() - } - - const EDGE_MASK = "linear-gradient(to top, transparent 0%, black 8px)" - const applyEdge = () => { - if (!view) return - edgeFade?.stop() - edgeFade = undefined - view.style.maskImage = EDGE_MASK - view.style.webkitMaskImage = EDGE_MASK - view.style.maskSize = "100% 100%" - view.style.maskRepeat = "no-repeat" - } - const clearEdge = () => { - if (!view) return - if (!active()) { - clearMaskStyles(view) - return - } - edgeFade?.stop() - const anim = animate(view, { maskSize: "100% 200%" }, props.spring ?? GROW_SPRING) - edgeFade = anim - anim.finished - .catch(() => {}) - .then(() => { - if (edgeFade !== anim || !view) return - clearMaskStyles(view) - edgeFade = undefined - }) - } - - createEffect( - on(height, (next, prev) => { - if (!view) return - if (!active()) { - resize?.stop() - resize = undefined - setView(next) - view.style.opacity = "" - clearEdge() - return - } - const collapsing = next === 0 && prev !== undefined && prev > 0 - const expanding = prev === 0 && next > 0 - resize?.stop() - view.style.opacity = "" - applyEdge() - const spring = props.spring ?? GROW_SPRING - const anim = collapsing - ? animate(view, noFade() ? { height: `${next}px` } : { height: `${next}px`, opacity: 0 }, spring) - : expanding - ? animate(view, noFade() ? { height: `${next}px` } : { height: `${next}px`, opacity: [0, 1] }, spring) - : animate(view, { height: `${next}px` }, spring) - resize = anim - anim.finished - .catch(() => {}) - .finally(() => { - view.style.opacity = "" - if (resize !== anim) return - setView(next) - resize = undefined - clearEdge() - }) - }), - ) - - onCleanup(() => { - shift?.stop() - resize?.stop() - edgeFade?.stop() - shift = undefined - resize = undefined - edgeFade = undefined - }) - - return ( -
-
- -
{props.fixed}
-
-
-
- -
{props.empty}
-
-
- - {(item, index) => ( -
- {props.render(item, index())} -
- )} -
-
-
-
-
-
- ) -} diff --git a/packages/ui/src/components/scroll-view.css b/packages/ui/src/components/scroll-view.css index a8574cc9f7..f6a49e241c 100644 --- a/packages/ui/src/components/scroll-view.css +++ b/packages/ui/src/components/scroll-view.css @@ -9,13 +9,6 @@ overflow-y: auto; scrollbar-width: none; outline: none; - display: block; - overflow-anchor: none; -} - -.scroll-view__viewport[data-reverse="true"] { - display: flex; - flex-direction: column-reverse; } .scroll-view__viewport::-webkit-scrollbar { @@ -52,6 +45,18 @@ background-color: var(--border-strong-base); } +.dark .scroll-view__thumb::after, +[data-theme="dark"] .scroll-view__thumb::after { + background-color: var(--border-weak-base); +} + +.dark .scroll-view__thumb:hover::after, +[data-theme="dark"] .scroll-view__thumb:hover::after, +.dark .scroll-view__thumb[data-dragging="true"]::after, +[data-theme="dark"] .scroll-view__thumb[data-dragging="true"]::after { + background-color: var(--border-strong-base); +} + .scroll-view__thumb[data-visible="true"] { opacity: 1; } diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index a8d3cf0f84..52ed39a465 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -1,18 +1,17 @@ -import { createSignal, onCleanup, onMount, splitProps, type ComponentProps, Show } from "solid-js" -import { animate, type AnimationPlaybackControls } from "motion" +import { createSignal, onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js" import { useI18n } from "../context/i18n" -import { FAST_SPRING } from "./motion" export interface ScrollViewProps extends ComponentProps<"div"> { viewportRef?: (el: HTMLDivElement) => void - reverse?: boolean + orientation?: "vertical" | "horizontal" // currently only vertical is fully implemented for thumb } export function ScrollView(props: ScrollViewProps) { const i18n = useI18n() + const merged = mergeProps({ orientation: "vertical" }, props) const [local, events, rest] = splitProps( - props, - ["class", "children", "viewportRef", "style", "reverse"], + merged, + ["class", "children", "viewportRef", "orientation", "style"], [ "onScroll", "onWheel", @@ -26,9 +25,9 @@ export function ScrollView(props: ScrollViewProps) { ], ) + let rootRef!: HTMLDivElement let viewportRef!: HTMLDivElement let thumbRef!: HTMLDivElement - let anim: AnimationPlaybackControls | undefined const [isHovered, setIsHovered] = createSignal(false) const [isDragging, setIsDragging] = createSignal(false) @@ -37,8 +36,6 @@ export function ScrollView(props: ScrollViewProps) { const [thumbTop, setThumbTop] = createSignal(0) const [showThumb, setShowThumb] = createSignal(false) - const reverse = () => local.reverse === true - const updateThumb = () => { if (!viewportRef) return const { scrollTop, scrollHeight, clientHeight } = viewportRef @@ -60,13 +57,9 @@ export function ScrollView(props: ScrollViewProps) { const maxScrollTop = scrollHeight - clientHeight const maxThumbTop = trackHeight - height - const top = (() => { - if (maxScrollTop <= 0) return 0 - if (!reverse()) return (scrollTop / maxScrollTop) * maxThumbTop - return ((maxScrollTop + scrollTop) / maxScrollTop) * maxThumbTop - })() + const top = maxScrollTop > 0 ? (scrollTop / maxScrollTop) * maxThumbTop : 0 - // Ensure thumb stays within bounds + // Ensure thumb stays within bounds (shouldn't be necessary due to math above, but good for safety) const boundedTop = trackPadding + Math.max(0, Math.min(top, maxThumbTop)) setThumbHeight(height) @@ -89,7 +82,6 @@ export function ScrollView(props: ScrollViewProps) { } onCleanup(() => { - stop() observer.disconnect() }) @@ -131,31 +123,6 @@ export function ScrollView(props: ScrollViewProps) { thumbRef.addEventListener("pointerup", onPointerUp) } - const stop = () => { - if (!anim) return - anim.stop() - anim = undefined - } - - const limit = (top: number) => { - const max = viewportRef.scrollHeight - viewportRef.clientHeight - if (reverse()) return Math.max(-max, Math.min(0, top)) - return Math.max(0, Math.min(max, top)) - } - - const glide = (top: number) => { - stop() - anim = animate(viewportRef.scrollTop, limit(top), { - ...FAST_SPRING, - onUpdate: (v) => { - viewportRef.scrollTop = v - }, - onComplete: () => { - anim = undefined - }, - }) - } - // Keybinds implementation // We ensure the viewport has a tabindex so it can receive focus // We can also explicitly catch PageUp/Down if we want smooth scroll or specific behavior, @@ -180,11 +147,11 @@ export function ScrollView(props: ScrollViewProps) { break case "Home": e.preventDefault() - glide(reverse() ? -(viewportRef.scrollHeight - viewportRef.clientHeight) : 0) + viewportRef.scrollTo({ top: 0, behavior: "smooth" }) break case "End": e.preventDefault() - glide(reverse() ? 0 : viewportRef.scrollHeight - viewportRef.clientHeight) + viewportRef.scrollTo({ top: viewportRef.scrollHeight, behavior: "smooth" }) break case "ArrowUp": e.preventDefault() @@ -199,6 +166,7 @@ export function ScrollView(props: ScrollViewProps) { return (
setIsHovered(true)} @@ -209,26 +177,16 @@ export function ScrollView(props: ScrollViewProps) {
{ updateThumb() if (typeof events.onScroll === "function") events.onScroll(e as any) }} - onWheel={(e) => { - if (e.deltaY) stop() - if (typeof events.onWheel === "function") events.onWheel(e as any) - }} - onTouchStart={(e) => { - stop() - if (typeof events.onTouchStart === "function") events.onTouchStart(e as any) - }} + onWheel={events.onWheel as any} + onTouchStart={events.onTouchStart as any} onTouchMove={events.onTouchMove as any} onTouchEnd={events.onTouchEnd as any} onTouchCancel={events.onTouchCancel as any} - onPointerDown={(e) => { - stop() - if (typeof events.onPointerDown === "function") events.onPointerDown(e as any) - }} + onPointerDown={events.onPointerDown as any} onClick={events.onClick as any} tabIndex={0} role="region" diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 56e060633b..eea9a13e4f 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -1,4 +1,5 @@ [data-component="session-turn"] { + --sticky-header-height: calc(var(--session-title-height, 0px) + 24px); height: 100%; min-height: 0; min-width: 0; @@ -25,7 +26,7 @@ align-items: flex-start; align-self: stretch; min-width: 0; - gap: 0px; + gap: 18px; overflow-anchor: none; } @@ -42,127 +43,30 @@ align-self: stretch; } - [data-slot="session-turn-assistant-lane"] { - width: 100%; - min-width: 0; - display: flex; - flex-direction: column; - align-self: stretch; - } - [data-slot="session-turn-thinking"] { display: flex; - flex-wrap: nowrap; align-items: center; gap: 8px; width: 100%; min-width: 0; - white-space: nowrap; color: var(--text-weak); font-family: var(--font-family-sans); font-size: var(--font-size-base); font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); - height: 36px; + line-height: 20px; + min-height: 20px; [data-component="spinner"] { width: 16px; height: 16px; } - - > [data-component="text-shimmer"] { - flex: 0 0 auto; - white-space: nowrap; - } - } - - [data-slot="session-turn-handoff-wrap"] { - width: 100%; - min-width: 0; - overflow: visible; - } - - [data-slot="session-turn-handoff"] { - width: 100%; - min-width: 0; - min-height: 37px; - position: relative; - } - - [data-slot="session-turn-thinking"] { - position: absolute; - inset: 0; - will-change: opacity, filter; - transition: - opacity 180ms ease-out, - filter 180ms ease-out, - transform 180ms ease-out; - } - - [data-slot="session-turn-thinking"][data-visible="false"] { - opacity: 0; - filter: blur(2px); - transform: translateY(1px); - pointer-events: none; - } - - [data-slot="session-turn-thinking"][data-visible="true"] { - opacity: 1; - filter: blur(0px); - transform: translateY(0px); - } - - [data-slot="session-turn-meta"] { - position: absolute; - inset: 0; - min-height: 37px; - display: flex; - align-items: center; - justify-content: flex-start; - gap: 10px; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s ease; - } - - [data-slot="session-turn-meta"][data-interrupted] { - gap: 12px; - } - - [data-slot="session-turn-meta"] [data-component="tooltip-trigger"] { - display: inline-flex; - width: fit-content; - } - - [data-slot="session-turn-message-container"]:hover [data-slot="session-turn-meta"][data-visible="true"], - [data-slot="session-turn-message-container"]:focus-within [data-slot="session-turn-meta"][data-visible="true"] { - opacity: 1; - pointer-events: auto; - } - - [data-slot="session-turn-meta-label"] { - user-select: none; - min-width: 0; - overflow: clip; - white-space: nowrap; - text-overflow: ellipsis; } [data-component="text-reveal"].session-turn-thinking-heading { flex: 1 1 auto; min-width: 0; - overflow: clip; - white-space: nowrap; - line-height: inherit; color: var(--text-weaker); font-weight: var(--font-weight-regular); - - [data-slot="text-reveal-track"], - [data-slot="text-reveal-entering"], - [data-slot="text-reveal-leaving"] { - min-height: 0; - line-height: inherit; - } } .error-card { @@ -180,7 +84,7 @@ display: flex; flex-direction: column; align-self: stretch; - gap: 0px; + gap: 12px; > :first-child > [data-component="markdown"]:first-child { margin-top: 0; @@ -205,7 +109,6 @@ [data-component="session-turn-diffs-trigger"] { width: 100%; - height: 36px; display: flex; align-items: center; justify-content: flex-start; @@ -215,7 +118,7 @@ [data-slot="session-turn-diffs-title"] { display: inline-flex; - align-items: center; + align-items: baseline; gap: 8px; } @@ -233,7 +136,7 @@ font-variant-numeric: tabular-nums; font-size: var(--font-size-base); font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); + line-height: var(--line-height-x-large); } [data-slot="session-turn-diffs-meta"] { @@ -269,10 +172,8 @@ [data-slot="session-turn-diff-path"] { display: flex; + flex-grow: 1; min-width: 0; - align-items: baseline; - overflow: clip; - white-space: nowrap; font-family: var(--font-family-sans); font-size: var(--font-size-small); @@ -280,22 +181,16 @@ } [data-slot="session-turn-diff-directory"] { - flex: 1 1 auto; - color: var(--text-weak); - min-width: 0; - overflow: clip; + 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: clip; - white-space: nowrap; color: var(--text-strong); font-weight: var(--font-weight-medium); } diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index f1aee802ec..3323a9fc66 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -3,27 +3,23 @@ import type { SessionStatus } from "@opencode-ai/sdk/v2" import { useData } from "../context" import { useFileComponent } from "../context/file" -import { same } from "@opencode-ai/util/array" import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" -import { createEffect, createMemo, createSignal, For, on, onCleanup, ParentProps, Show } from "solid-js" +import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { Dynamic } from "solid-js/web" -import { GrowBox } from "./grow-box" -import { AssistantParts, UserMessageDisplay, Part, PART_MAPPING } from "./message-part" +import { AssistantParts, Message, Part, PART_MAPPING } from "./message-part" import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" import { Collapsible } from "./collapsible" import { DiffChanges } from "./diff-changes" import { Icon } from "./icon" -import { IconButton } from "./icon-button" import { TextShimmer } from "./text-shimmer" -import { TextReveal } from "./text-reveal" -import { list } from "./text-utils" import { SessionRetry } from "./session-retry" -import { Tooltip } from "./tooltip" +import { TextReveal } from "./text-reveal" import { createAutoScroll } from "../hooks" import { useI18n } from "../context/i18n" + function record(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value) } @@ -77,12 +73,18 @@ function unwrap(message: string) { return message } +function same(a: readonly T[], b: readonly T[]) { + if (a === b) return true + if (a.length !== b.length) return false + return a.every((x, i) => x === b[i]) +} + +function list(value: T[] | undefined | null, fallback: T[]) { + if (Array.isArray(value)) return value + return fallback +} + const hidden = new Set(["todowrite", "todoread"]) -const emptyMessages: MessageType[] = [] -const emptyAssistant: AssistantMessage[] = [] -const emptyDiffs: FileDiff[] = [] -const idle: SessionStatus = { type: "idle" as const } -const handoffHoldMs = 120 function partState(part: PartType, showReasoningSummaries: boolean) { if (part.type === "tool") { @@ -139,7 +141,6 @@ export function SessionTurn( props: ParentProps<{ sessionID: string messageID: string - animate?: boolean showReasoningSummaries?: boolean shellToolDefaultOpen?: boolean editToolDefaultOpen?: boolean @@ -158,7 +159,11 @@ export function SessionTurn( const i18n = useI18n() const fileComponent = useFileComponent() + const emptyMessages: MessageType[] = [] const emptyParts: PartType[] = [] + const emptyAssistant: AssistantMessage[] = [] + const emptyDiffs: FileDiff[] = [] + const idle = { type: "idle" as const } const allMessages = createMemo(() => list(data.store.message?.[props.sessionID], emptyMessages)) @@ -186,8 +191,42 @@ export function SessionTurn( return msg }) - const active = createMemo(() => props.active ?? false) - const queued = createMemo(() => props.queued ?? false) + const pending = createMemo(() => { + if (typeof props.active === "boolean" && typeof props.queued === "boolean") return + const messages = allMessages() ?? emptyMessages + return messages.findLast( + (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number", + ) + }) + + const pendingUser = createMemo(() => { + const item = pending() + if (!item?.parentID) return + const messages = allMessages() ?? emptyMessages + const result = Binary.search(messages, item.parentID, (m) => m.id) + const msg = result.found ? messages[result.index] : messages.find((m) => m.id === item.parentID) + if (!msg || msg.role !== "user") return + return msg + }) + + const active = createMemo(() => { + if (typeof props.active === "boolean") return props.active + const msg = message() + const parent = pendingUser() + if (!msg || !parent) return false + return parent.id === msg.id + }) + + const queued = createMemo(() => { + if (typeof props.queued === "boolean") return props.queued + const id = message()?.id + if (!id) return false + if (!pendingUser()) return false + const item = pending() + if (!item) return false + return id > item.id + }) + const parts = createMemo(() => { const msg = message() if (!msg) return emptyParts @@ -250,7 +289,7 @@ export function SessionTurn( const error = createMemo( () => assistantMessages().find((m) => m.error && m.error.name !== "MessageAbortedError")?.error, ) - const assistantCopyPart = createMemo(() => { + const showAssistantCopyPartID = createMemo(() => { const messages = assistantMessages() for (let i = messages.length - 1; i >= 0; i--) { @@ -260,18 +299,13 @@ export function SessionTurn( const parts = list(data.store.part?.[message.id], emptyParts) for (let j = parts.length - 1; j >= 0; j--) { const part = parts[j] - if (!part || part.type !== "text") continue - const text = part.text?.trim() - if (!text) continue - return { - id: part.id, - text, - message, - } + if (!part || part.type !== "text" || !part.text?.trim()) continue + return part.id } } + + return undefined }) - const assistantCopyPartID = createMemo(() => assistantCopyPart()?.id ?? null) const errorText = createMemo(() => { const msg = error()?.data?.message if (typeof msg === "string") return unwrap(msg) @@ -279,14 +313,18 @@ export function SessionTurn( return unwrap(String(msg)) }) - const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle) - const working = createMemo(() => { - if (status().type === "idle") return false - if (!message()) return false - return active() + const status = createMemo(() => { + if (props.status !== undefined) return props.status + if (typeof props.active === "boolean" && !props.active) return idle + return data.store.session_status[props.sessionID] ?? idle }) + const working = createMemo(() => status().type !== "idle" && active()) const showReasoningSummaries = createMemo(() => props.showReasoningSummaries ?? true) - const showDiffSummary = createMemo(() => edited() > 0 && !working()) + + const assistantCopyPartID = createMemo(() => { + if (working()) return null + return showAssistantCopyPartID() ?? null + }) const turnDurationMs = createMemo(() => { const start = message()?.time.created if (typeof start !== "number") return undefined @@ -326,109 +364,13 @@ export function SessionTurn( .filter((text): text is string => !!text) .at(-1), ) - const thinking = createMemo(() => { + const showThinking = createMemo(() => { if (!working() || !!error()) return false if (queued()) return false if (status().type === "retry") return false if (showReasoningSummaries()) return assistantVisible() === 0 return true }) - const hasAssistant = createMemo(() => assistantMessages().length > 0) - const animateEnabled = createMemo(() => props.animate !== false) - const [live, setLive] = createSignal(false) - const thinkingOpen = createMemo(() => thinking() && (live() || !animateEnabled())) - const metaOpen = createMemo(() => !working() && !!assistantCopyPart()) - const duration = createMemo(() => { - const ms = turnDurationMs() - if (typeof ms !== "number" || ms < 0) return "" - - const total = Math.round(ms / 1000) - if (total < 60) return `${total}s` - - const minutes = Math.floor(total / 60) - const seconds = total % 60 - return `${minutes}m ${seconds}s` - }) - const meta = createMemo(() => { - const item = assistantCopyPart() - if (!item) return "" - - const agent = item.message.agent ? item.message.agent[0]?.toUpperCase() + item.message.agent.slice(1) : "" - const model = item.message.modelID - ? (data.store.provider?.all?.find((provider) => provider.id === item.message.providerID)?.models?.[ - item.message.modelID - ]?.name ?? item.message.modelID) - : "" - return [agent, model, duration()].filter((value) => !!value).join("\u00A0\u00B7\u00A0") - }) - const [copied, setCopied] = createSignal(false) - const [handoffHold, setHandoffHold] = createSignal(false) - const thinkingVisible = createMemo(() => thinkingOpen() || handoffHold()) - const handoffOpen = createMemo(() => thinkingVisible() || metaOpen()) - const lane = createMemo(() => hasAssistant() || handoffOpen()) - - let liveFrame: number | undefined - let copiedTimer: ReturnType | undefined - let handoffTimer: ReturnType | undefined - - const copyAssistant = async () => { - const text = assistantCopyPart()?.text - if (!text) return - - await navigator.clipboard.writeText(text) - setCopied(true) - if (copiedTimer !== undefined) clearTimeout(copiedTimer) - copiedTimer = setTimeout(() => { - copiedTimer = undefined - setCopied(false) - }, 2000) - } - - createEffect( - on( - () => [animateEnabled(), working()] as const, - ([enabled, isWorking]) => { - if (liveFrame !== undefined) { - cancelAnimationFrame(liveFrame) - liveFrame = undefined - } - if (!enabled || !isWorking || live()) return - liveFrame = requestAnimationFrame(() => { - liveFrame = undefined - setLive(true) - }) - }, - ), - ) - - createEffect( - on( - () => [thinkingOpen(), metaOpen()] as const, - ([thinkingNow, metaNow]) => { - if (handoffTimer !== undefined) { - clearTimeout(handoffTimer) - handoffTimer = undefined - } - - if (thinkingNow) { - setHandoffHold(true) - return - } - - if (metaNow) { - setHandoffHold(false) - return - } - - if (!handoffHold()) return - handoffTimer = setTimeout(() => { - handoffTimer = undefined - setHandoffHold(false) - }, handoffHoldMs) - }, - { defer: true }, - ), - ) const autoScroll = createAutoScroll({ working, @@ -436,119 +378,6 @@ export function SessionTurn( overflowAnchor: "dynamic", }) - onCleanup(() => { - if (liveFrame !== undefined) cancelAnimationFrame(liveFrame) - if (copiedTimer !== undefined) clearTimeout(copiedTimer) - if (handoffTimer !== undefined) clearTimeout(handoffTimer) - }) - - const turnDiffSummary = () => ( -
- - -
-
- {i18n.t("ui.sessionReview.change.modified")} - - {edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")} - -
- - -
-
-
-
- - -
- setExpanded(Array.isArray(value) ? value : value ? [value] : [])} - > - - {(diff) => { - const active = createMemo(() => expanded().includes(diff.file)) - const [visible, setVisible] = createSignal(false) - - createEffect( - on( - active, - (value) => { - if (!value) { - setVisible(false) - return - } - - requestAnimationFrame(() => { - if (!active()) return - setVisible(true) - }) - }, - { defer: true }, - ), - ) - - return ( - - - -
- - - {`\u202A${getDirectory(diff.file)}\u202C`} - - {getFilename(diff.file)} - -
- - - - - - -
-
-
-
- - -
- -
-
-
-
- ) - }} -
-
-
-
-
-
-
- ) - - const divider = (label: string) => ( -
-
- - - {label} - - -
-
- ) - return (
- {(msg) => ( -
-
- +
+ +
+ +
+ +
+
+ 0}> +
+
- - {(part) => ( - -
- -
-
- )} -
-
- -
- -
+
+ +
+ + + - -
-
- - +
+ + + 0 && !working()}> +
+ + +
+
+ {i18n.t("ui.sessionReview.change.modified")} + + {edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")} + +
+ + +
+
- -
- + + +
+ setExpanded(Array.isArray(value) ? value : value ? [value] : [])} > - event.preventDefault()} - onClick={() => void copyAssistant()} - aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyResponse")} - /> - - - - {meta()} - - + + {(diff) => { + const active = createMemo(() => expanded().includes(diff.file)) + const [visible, setVisible] = createSignal(false) + + createEffect( + on( + active, + (value) => { + if (!value) { + setVisible(false) + return + } + + requestAnimationFrame(() => { + if (!active()) return + setVisible(true) + }) + }, + { defer: true }, + ), + ) + + return ( + + + +
+ + + + {`\u202A${getDirectory(diff.file)}\u202C`} + + + {getFilename(diff.file)} + +
+ + + + + + +
+
+
+
+ + +
+ +
+
+
+
+ ) + }} +
+
-
- + +
- - {divider(i18n.t("ui.message.interrupted"))} - - - - {turnDiffSummary()} - - - - {errorText()} - - -
- )} + + + + {errorText()} + + +
{props.children}
diff --git a/packages/ui/src/components/shell-rolling-results.tsx b/packages/ui/src/components/shell-rolling-results.tsx deleted file mode 100644 index 0210e46e0e..0000000000 --- a/packages/ui/src/components/shell-rolling-results.tsx +++ /dev/null @@ -1,291 +0,0 @@ -import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js" -import stripAnsi from "strip-ansi" -import type { ToolPart } from "@opencode-ai/sdk/v2" -import { useReducedMotion } from "../hooks/use-reduced-motion" -import { useI18n } from "../context/i18n" -import { RollingResults } from "./rolling-results" -import { Icon } from "./icon" -import { IconButton } from "./icon-button" -import { TextShimmer } from "./text-shimmer" -import { Tooltip } from "./tooltip" -import { GROW_SPRING } from "./motion" -import { useSpring } from "./motion-spring" -import { busy, createThrottledValue, updateScrollMask, useCollapsible, useRowWipe, useToolFade } from "./tool-utils" - -function ShellRollingSubtitle(props: { text: string; animate?: boolean }) { - let ref: HTMLSpanElement | undefined - useToolFade(() => ref, { wipe: true, animate: props.animate }) - - return ( - - {props.text} - - ) -} - -function firstLine(text: string) { - return text - .split(/\r\n|\n|\r/g) - .map((item) => item.trim()) - .find((item) => item.length > 0) -} - -function shellRows(output: string) { - const rows: { id: string; text: string }[] = [] - const lines = output - .split(/\r\n|\n|\r/g) - .map((item) => item.trimEnd()) - .filter((item) => item.length > 0) - const start = Math.max(0, lines.length - 80) - for (let i = start; i < lines.length; i++) { - rows.push({ id: `line:${i}`, text: lines[i]! }) - } - - return rows -} - -function ShellRollingCommand(props: { text: string; animate?: boolean }) { - let ref: HTMLSpanElement | undefined - useToolFade(() => ref, { wipe: true, animate: props.animate }) - - return ( -
- - $ {props.text} - -
- ) -} - -function ShellExpanded(props: { cmd: string; out: string; open: boolean }) { - const i18n = useI18n() - const rows = 10 - const rowHeight = 22 - const max = rows * rowHeight - - let contentRef: HTMLDivElement | undefined - let bodyRef: HTMLDivElement | undefined - let scrollRef: HTMLDivElement | undefined - let topRef: HTMLDivElement | undefined - const [copied, setCopied] = createSignal(false) - const [cap, setCap] = createSignal(max) - - const updateMask = () => { - if (scrollRef) updateScrollMask(scrollRef) - } - - const resize = () => { - const top = Math.ceil(topRef?.getBoundingClientRect().height ?? 0) - setCap(Math.max(rowHeight * 2, max - top - (props.out ? 1 : 0))) - } - - const measure = () => { - resize() - return Math.ceil(bodyRef?.getBoundingClientRect().height ?? 0) - } - - onMount(() => { - resize() - if (!topRef) return - const obs = new ResizeObserver(resize) - obs.observe(topRef) - onCleanup(() => obs.disconnect()) - }) - - createEffect(() => { - props.cmd - props.out - queueMicrotask(() => { - resize() - updateMask() - }) - }) - - useCollapsible({ - content: () => contentRef, - body: () => bodyRef, - open: () => props.open, - measure, - onOpen: updateMask, - }) - - const handleCopy = async (e: MouseEvent) => { - e.stopPropagation() - const cmd = props.cmd ? `$ ${props.cmd}` : "" - const text = `${cmd}${props.out ? `${cmd ? "\n\n" : ""}${props.out}` : ""}` - if (!text) return - await navigator.clipboard.writeText(text) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } - - return ( -
-
-
-
-
- $ - {props.cmd} -
-
- - e.preventDefault()} - onClick={handleCopy} - aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")} - /> - -
-
- - <> -
-
-
-                  {props.out}
-                
-
- - -
-
-
- ) -} - -export function ShellRollingResults(props: { part: ToolPart; animate?: boolean; defaultOpen?: boolean }) { - const i18n = useI18n() - const reduce = useReducedMotion() - const wiped = new Set() - const [mounted, setMounted] = createSignal(false) - const [open, setOpen] = createSignal(props.defaultOpen ?? true) - onMount(() => setMounted(true)) - const state = createMemo(() => props.part.state as Record) - const pending = createMemo(() => busy(props.part.state.status)) - const expanded = createMemo(() => open() && !pending()) - const previewOpen = createMemo(() => open() && pending()) - const command = createMemo(() => { - const value = state().input?.command ?? state().metadata?.command - if (typeof value === "string") return value - return "" - }) - const subtitle = createMemo(() => { - const value = state().input?.description ?? state().metadata?.description - if (typeof value === "string" && value.trim().length > 0) return value - return firstLine(command()) ?? "" - }) - const output = createMemo(() => { - const value = state().output ?? state().metadata?.output - if (typeof value === "string") return value - return "" - }) - const skip = () => reduce() || props.animate === false - const opacity = useSpring(() => (mounted() ? 1 : 0), GROW_SPRING) - const blur = useSpring(() => (mounted() ? 0 : 2), GROW_SPRING) - const previewOpacity = useSpring(() => (previewOpen() ? 1 : 0), GROW_SPRING) - const previewBlur = useSpring(() => (previewOpen() ? 0 : 2), GROW_SPRING) - const headerHeight = useSpring(() => (mounted() ? 37 : 0), GROW_SPRING) - let headerClipRef: HTMLDivElement | undefined - const handleHeaderClick = () => { - const el = headerClipRef - const viewport = el?.closest(".scroll-view__viewport") as HTMLElement | null - const beforeY = el?.getBoundingClientRect().top ?? 0 - setOpen((prev) => !prev) - if (viewport && el) { - requestAnimationFrame(() => { - const afterY = el.getBoundingClientRect().top - const delta = afterY - beforeY - if (delta !== 0) viewport.scrollTop += delta - }) - } - } - const line = createMemo(() => firstLine(command())) - const fixed = createMemo(() => { - const value = line() - if (!value) return - return - }) - const text = createThrottledValue(() => stripAnsi(output())) - const rows = createMemo(() => shellRows(text())) - - return ( -
-
-
- - - - {(text) => } - - - - - -
-
-
- row.id} - render={(row) => { - const [textRef, setTextRef] = createSignal() - useRowWipe({ - id: () => row.id, - text: () => row.text, - ref: textRef, - seen: wiped, - }) - return ( -
- - {row.text} - -
- ) - }} - /> -
- -
- ) -} diff --git a/packages/ui/src/components/shell-submessage.css b/packages/ui/src/components/shell-submessage.css index 9f19c2d152..f72ba3fc75 100644 --- a/packages/ui/src/components/shell-submessage.css +++ b/packages/ui/src/components/shell-submessage.css @@ -1,13 +1,23 @@ [data-component="shell-submessage"] { min-width: 0; max-width: 100%; - display: inline-block; + display: inline-flex; + align-items: baseline; vertical-align: baseline; } +[data-component="shell-submessage"] [data-slot="shell-submessage-width"] { + min-width: 0; + max-width: 100%; + display: inline-flex; + align-items: baseline; + overflow: hidden; +} + [data-component="shell-submessage"] [data-slot="shell-submessage-value"] { display: inline-block; vertical-align: baseline; min-width: 0; + line-height: inherit; white-space: nowrap; } diff --git a/packages/ui/src/components/text-reveal.css b/packages/ui/src/components/text-reveal.css index 7939322e6d..f799962f09 100644 --- a/packages/ui/src/components/text-reveal.css +++ b/packages/ui/src/components/text-reveal.css @@ -4,14 +4,14 @@ * Instead of sliding text through a fixed mask (odometer style), * the mask itself sweeps across each span to reveal/hide text. * - * Direction: bottom-to-top. New text rises in from below, old text exits upward. + * Direction: top-to-bottom. New text drops in from above, old text exits downward. * - * Entering: gradient reveals bottom-to-top (bottom of text appears first). + * Entering: gradient reveals top-to-bottom (top of text appears first). * gradient(to bottom, white 33%, transparent 33%+edge) * pos 0 100% = transparent covers element = hidden * pos 0 0% = white covers element = visible * - * Leaving: gradient hides bottom-to-top (bottom of text disappears first). + * Leaving: gradient hides top-to-bottom (top of text disappears first). * gradient(to top, white 33%, transparent 33%+edge) * pos 0 100% = white covers element = visible * pos 0 0% = transparent covers element = hidden @@ -56,17 +56,17 @@ transition-timing-function: var(--_spring); } - /* ── entering: reveal bottom-to-top ── - * Gradient(to bottom): white at top, transparent at bottom of mask. - * Settled pos 0 0% = white covers element = visible - * Swap pos 0 100% = transparent covers = hidden - * Rises from below: translateY(travel) → translateY(0) + /* ── entering: reveal top-to-bottom ── + * Gradient(to top): white at bottom, transparent at top of mask. + * Settled pos 0 100% = white covers element = visible + * Swap pos 0 0% = transparent covers = hidden + * Slides from above: translateY(-travel) → translateY(0) */ [data-slot="text-reveal-entering"] { - mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); - -webkit-mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); - mask-position: 0 0%; - -webkit-mask-position: 0 0%; + mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); + -webkit-mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); + mask-position: 0 100%; + -webkit-mask-position: 0 100%; transition-property: mask-position, -webkit-mask-position, @@ -74,37 +74,37 @@ transform: translateY(0); } - /* ── leaving: hide bottom-to-top + slide upward ── - * Gradient(to top): white at bottom, transparent at top of mask. - * Swap pos 0 100% = white covers element = visible - * Settled pos 0 0% = transparent covers = hidden - * Slides up: translateY(0) → translateY(-travel) + /* ── leaving: hide top-to-bottom + slide downward ── + * Gradient(to bottom): white at top, transparent at bottom of mask. + * Swap pos 0 0% = white covers element = visible + * Settled pos 0 100% = transparent covers = hidden + * Slides down: translateY(0) → translateY(travel) */ [data-slot="text-reveal-leaving"] { - mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); - -webkit-mask-image: linear-gradient(to top, white 33%, transparent calc(33% + var(--_edge))); - mask-position: 0 0%; - -webkit-mask-position: 0 0%; + mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); + -webkit-mask-image: linear-gradient(to bottom, white 33%, transparent calc(33% + var(--_edge))); + mask-position: 0 100%; + -webkit-mask-position: 0 100%; transition-property: mask-position, -webkit-mask-position, transform; - transform: translateY(calc(var(--_travel) * -1)); + transform: translateY(var(--_travel)); } /* ── swapping: instant reset ── - * Snap entering to hidden (below), leaving to visible (center). + * Snap entering to hidden (above), leaving to visible (center). */ &[data-swapping="true"] [data-slot="text-reveal-entering"] { - mask-position: 0 100%; - -webkit-mask-position: 0 100%; - transform: translateY(var(--_travel)); + mask-position: 0 0%; + -webkit-mask-position: 0 0%; + transform: translateY(calc(var(--_travel) * -1)); transition-duration: 0ms !important; } &[data-swapping="true"] [data-slot="text-reveal-leaving"] { - mask-position: 0 100%; - -webkit-mask-position: 0 100%; + mask-position: 0 0%; + -webkit-mask-position: 0 0%; transform: translateY(0); transition-duration: 0ms !important; } @@ -126,14 +126,15 @@ &[data-truncate="true"] [data-slot="text-reveal-track"] { width: 100%; min-width: 0; - overflow: clip; + overflow: hidden; } &[data-truncate="true"] [data-slot="text-reveal-entering"], &[data-truncate="true"] [data-slot="text-reveal-leaving"] { min-width: 0; width: 100%; - overflow: clip; + overflow: hidden; + text-overflow: ellipsis; } } diff --git a/packages/ui/src/components/text-reveal.tsx b/packages/ui/src/components/text-reveal.tsx index edf5dbf837..c4fe1302f0 100644 --- a/packages/ui/src/components/text-reveal.tsx +++ b/packages/ui/src/components/text-reveal.tsx @@ -1,13 +1,4 @@ import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js" -import { useReducedMotion } from "../hooks/use-reduced-motion" -import { - animate, - type AnimationPlaybackControls, - clearFadeStyles, - clearMaskStyles, - GROW_SPRING, - WIPE_MASK, -} from "./motion" const px = (value: number | string | undefined, fallback: number) => { if (typeof value === "number") return `${value}px` @@ -26,11 +17,6 @@ const pct = (value: number | undefined, fallback: number) => { return `${v}%` } -const clearWipe = (el: HTMLElement) => { - clearFadeStyles(el) - clearMaskStyles(el) -} - export function TextReveal(props: { text?: string class?: string @@ -53,8 +39,10 @@ export function TextReveal(props: { let outRef: HTMLSpanElement | undefined let rootRef: HTMLSpanElement | undefined let frame: number | undefined + const win = () => inRef?.scrollWidth ?? 0 const wout = () => outRef?.scrollWidth ?? 0 + const widen = (next: number) => { if (next <= 0) return if (props.growOnly ?? true) { @@ -63,14 +51,21 @@ export function TextReveal(props: { } setWidth(`${next}px`) } + createEffect( on( () => props.text, (next, prev) => { if (next === prev) return + if (typeof next === "string" && typeof prev === "string" && next.startsWith(prev)) { + setCur(next) + widen(win()) + return + } setSwapping(true) setOld(prev) setCur(next) + if (typeof requestAnimationFrame !== "function") { widen(Math.max(win(), wout())) rootRef?.offsetHeight @@ -138,95 +133,3 @@ export function TextReveal(props: { ) } - -export function TextWipe(props: { text?: string; class?: string; delay?: number; animate?: boolean }) { - let ref: HTMLSpanElement | undefined - let frame: number | undefined - let anim: AnimationPlaybackControls | undefined - const reduce = useReducedMotion() - - const run = () => { - if (props.animate === false) return - const el = ref - if (!el || !props.text || typeof window === "undefined") return - if (reduce()) return - - const mask = - typeof CSS !== "undefined" && - (CSS.supports("mask-image", "linear-gradient(to right, black, transparent)") || - CSS.supports("-webkit-mask-image", "linear-gradient(to right, black, transparent)")) - - anim?.stop() - if (frame !== undefined && typeof cancelAnimationFrame === "function") { - cancelAnimationFrame(frame) - frame = undefined - } - - el.style.opacity = "0" - el.style.filter = "blur(3px)" - el.style.transform = "translateX(-0.06em)" - - if (mask) { - el.style.maskImage = WIPE_MASK - el.style.webkitMaskImage = WIPE_MASK - el.style.maskSize = "240% 100%" - el.style.webkitMaskSize = "240% 100%" - el.style.maskRepeat = "no-repeat" - el.style.webkitMaskRepeat = "no-repeat" - el.style.maskPosition = "100% 0%" - el.style.webkitMaskPosition = "100% 0%" - } - - if (typeof requestAnimationFrame !== "function") { - clearWipe(el) - return - } - - frame = requestAnimationFrame(() => { - frame = undefined - const node = ref - if (!node) return - anim = mask - ? animate( - node, - { opacity: 1, filter: "blur(0px)", transform: "translateX(0)", maskPosition: "0% 0%" }, - { ...GROW_SPRING, delay: props.delay ?? 0 }, - ) - : animate( - node, - { opacity: 1, filter: "blur(0px)", transform: "translateX(0)" }, - { ...GROW_SPRING, delay: props.delay ?? 0 }, - ) - - anim?.finished.then(() => { - const value = ref - if (!value) return - clearWipe(value) - }) - }) - } - - createEffect( - on( - () => [props.text, props.animate] as const, - ([text, enabled]) => { - if (!text || enabled === false) { - if (ref) clearWipe(ref) - return - } - run() - }, - ), - ) - - onCleanup(() => { - if (frame !== undefined && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame) - anim?.stop() - }) - - return ( - - {props.text ?? "\u00A0"} - - ) -} diff --git a/packages/ui/src/components/text-shimmer.css b/packages/ui/src/components/text-shimmer.css index bd1437c273..f042dd2d86 100644 --- a/packages/ui/src/components/text-shimmer.css +++ b/packages/ui/src/components/text-shimmer.css @@ -1,11 +1,11 @@ [data-component="text-shimmer"] { --text-shimmer-step: 45ms; - --text-shimmer-duration: 2000ms; + --text-shimmer-duration: 1200ms; --text-shimmer-swap: 220ms; --text-shimmer-index: 0; --text-shimmer-angle: 90deg; --text-shimmer-spread: 5.2ch; - --text-shimmer-size: 600%; + --text-shimmer-size: 360%; --text-shimmer-base-color: var(--text-weak); --text-shimmer-peak-color: var(--text-strong); --text-shimmer-sweep: linear-gradient( @@ -16,17 +16,15 @@ ); --text-shimmer-base: linear-gradient(var(--text-shimmer-base-color), var(--text-shimmer-base-color)); - display: inline-block; - vertical-align: baseline; + display: inline-flex; + align-items: baseline; font: inherit; letter-spacing: inherit; line-height: inherit; } [data-component="text-shimmer"] [data-slot="text-shimmer-char"] { - display: inline-block; - position: relative; - vertical-align: baseline; + display: inline-grid; white-space: pre; font: inherit; letter-spacing: inherit; @@ -35,7 +33,7 @@ [data-component="text-shimmer"] [data-slot="text-shimmer-char-base"], [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { - display: inline-block; + grid-area: 1 / 1; white-space: pre; transition: opacity var(--text-shimmer-swap) ease-out; font: inherit; @@ -44,14 +42,11 @@ } [data-component="text-shimmer"] [data-slot="text-shimmer-char-base"] { - position: relative; color: inherit; opacity: 1; } [data-component="text-shimmer"] [data-slot="text-shimmer-char-shimmer"] { - position: absolute; - inset: 0; color: var(--text-weaker); opacity: 0; } diff --git a/packages/ui/src/components/text-shimmer.tsx b/packages/ui/src/components/text-shimmer.tsx index 0d797e5c1f..3ab077d92d 100644 --- a/packages/ui/src/components/text-shimmer.tsx +++ b/packages/ui/src/components/text-shimmer.tsx @@ -37,16 +37,6 @@ export const TextShimmer = (props: { clearTimeout(timer) }) - const len = createMemo(() => Math.max(text().length, 1)) - const shimmerSize = createMemo(() => Math.max(300, Math.round(200 + 1400 / len()))) - - // duration = len × (size - 1) / velocity → uniform perceived sweep speed - const VELOCITY = 0.01375 // ch per ms, ~10% faster than original 0.0125 baseline - const shimmerDuration = createMemo(() => { - const s = shimmerSize() / 100 - return Math.max(1000, Math.min(2500, Math.round((len() * (s - 1)) / VELOCITY))) - }) - return ( (props: { style={{ "--text-shimmer-swap": `${swap}ms`, "--text-shimmer-index": `${offset()}`, - "--text-shimmer-size": `${shimmerSize()}%`, - "--text-shimmer-duration": `${shimmerDuration()}ms`, }} > diff --git a/packages/ui/src/components/text-utils.ts b/packages/ui/src/components/text-utils.ts deleted file mode 100644 index c094b5e65f..0000000000 --- a/packages/ui/src/components/text-utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** Find the longest common character prefix between two strings. */ -export function commonPrefix(a: string, b: string) { - const ac = Array.from(a) - const bc = Array.from(b) - let i = 0 - while (i < ac.length && i < bc.length && ac[i] === bc[i]) i++ - return { - prefix: ac.slice(0, i).join(""), - aSuffix: ac.slice(i).join(""), - bSuffix: bc.slice(i).join(""), - } -} - -export function list(value: T[] | undefined | null, fallback: T[]): T[] { - if (Array.isArray(value)) return value - return fallback -} diff --git a/packages/ui/src/components/tool-count-label.css b/packages/ui/src/components/tool-count-label.css index 4ed46e50b5..11a33ff5d1 100644 --- a/packages/ui/src/components/tool-count-label.css +++ b/packages/ui/src/components/tool-count-label.css @@ -27,10 +27,10 @@ grid-template-columns: 0fr; opacity: 0; filter: blur(calc(var(--tool-motion-blur, 2px) * 0.42)); - overflow: clip; + overflow: hidden; transform: translateX(-0.04em); transition-property: grid-template-columns, opacity, filter, transform; - transition-duration: 800ms, 400ms, 400ms, 800ms; + transition-duration: 250ms, 250ms, 250ms, 250ms; transition-timing-function: var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), ease-out, ease-out, var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); @@ -45,7 +45,7 @@ [data-slot="tool-count-label-suffix-inner"] { min-width: 0; - overflow: clip; + overflow: hidden; white-space: pre; } } diff --git a/packages/ui/src/components/tool-count-label.tsx b/packages/ui/src/components/tool-count-label.tsx index c374d2d376..67e861cdcb 100644 --- a/packages/ui/src/components/tool-count-label.tsx +++ b/packages/ui/src/components/tool-count-label.tsx @@ -1,6 +1,5 @@ import { createMemo } from "solid-js" import { AnimatedNumber } from "./animated-number" -import { commonPrefix } from "./text-utils" function split(text: string) { const match = /{{\s*count\s*}}/.exec(text) @@ -12,23 +11,35 @@ function split(text: string) { } } +function common(one: string, other: string) { + const a = Array.from(one) + const b = Array.from(other) + let i = 0 + while (i < a.length && i < b.length && a[i] === b[i]) i++ + return { + stem: a.slice(0, i).join(""), + one: a.slice(i).join(""), + other: b.slice(i).join(""), + } +} + export function AnimatedCountLabel(props: { count: number; one: string; other: string; class?: string }) { const one = createMemo(() => split(props.one)) const other = createMemo(() => split(props.other)) const singular = createMemo(() => Math.round(props.count) === 1) const active = createMemo(() => (singular() ? one() : other())) - const suffix = createMemo(() => commonPrefix(one().after, other().after)) + const suffix = createMemo(() => common(one().after, other().after)) const splitSuffix = createMemo( () => one().before === other().before && (one().after.startsWith(other().after) || other().after.startsWith(one().after)), ) const before = createMemo(() => (splitSuffix() ? one().before : active().before)) - const stem = createMemo(() => (splitSuffix() ? suffix().prefix : active().after)) + const stem = createMemo(() => (splitSuffix() ? suffix().stem : active().after)) const tail = createMemo(() => { if (!splitSuffix()) return "" - if (singular()) return suffix().aSuffix - return suffix().bSuffix + if (singular()) return suffix().one + return suffix().other }) const showTail = createMemo(() => splitSuffix() && tail().length > 0) diff --git a/packages/ui/src/components/tool-count-summary.css b/packages/ui/src/components/tool-count-summary.css index 435ed95fe6..da8455267c 100644 --- a/packages/ui/src/components/tool-count-summary.css +++ b/packages/ui/src/components/tool-count-summary.css @@ -10,12 +10,12 @@ opacity: 1; filter: blur(0); transform: translateY(0) scale(1); - overflow: clip; + overflow: hidden; transform-origin: left center; transition-property: grid-template-columns, opacity, filter, transform; transition-duration: - var(--tool-motion-spring-ms, 800ms), var(--tool-motion-fade-ms, 400ms), var(--tool-motion-fade-ms, 400ms), - var(--tool-motion-spring-ms, 800ms); + var(--tool-motion-spring-ms, 480ms), var(--tool-motion-fade-ms, 240ms), var(--tool-motion-fade-ms, 280ms), + var(--tool-motion-spring-ms, 480ms); transition-timing-function: var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), ease-out, ease-out, var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); @@ -35,12 +35,12 @@ opacity: 0; filter: blur(var(--tool-motion-blur, 2px)); transform: translateY(0.06em) scale(0.985); - overflow: clip; + overflow: hidden; transform-origin: left center; transition-property: grid-template-columns, opacity, filter, transform; transition-duration: - var(--tool-motion-spring-ms, 800ms), var(--tool-motion-fade-ms, 400ms), var(--tool-motion-fade-ms, 400ms), - var(--tool-motion-spring-ms, 800ms); + var(--tool-motion-spring-ms, 480ms), var(--tool-motion-fade-ms, 280ms), var(--tool-motion-fade-ms, 320ms), + var(--tool-motion-spring-ms, 480ms); transition-timing-function: var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)), ease-out, ease-out, var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); @@ -55,7 +55,7 @@ [data-slot="tool-count-summary-empty-inner"] { min-width: 0; - overflow: clip; + overflow: hidden; white-space: nowrap; } @@ -63,7 +63,7 @@ display: inline-flex; align-items: baseline; min-width: 0; - overflow: clip; + overflow: hidden; white-space: nowrap; } @@ -75,11 +75,12 @@ margin-right: 0; opacity: 0; filter: blur(calc(var(--tool-motion-blur, 2px) * 0.55)); - overflow: clip; + overflow: hidden; transform: translateX(-0.08em); transition-property: opacity, filter, transform; transition-duration: - var(--tool-motion-fade-ms, 400ms), var(--tool-motion-fade-ms, 400ms), var(--tool-motion-fade-ms, 400ms); + calc(var(--tool-motion-fade-ms, 200ms) * 0.75), calc(var(--tool-motion-fade-ms, 220ms) * 0.75), + calc(var(--tool-motion-fade-ms, 220ms) * 0.6); transition-timing-function: ease-out, ease-out, ease-out; } diff --git a/packages/ui/src/components/tool-status-title.css b/packages/ui/src/components/tool-status-title.css index 050f5e390a..d4415bd2da 100644 --- a/packages/ui/src/components/tool-status-title.css +++ b/packages/ui/src/components/tool-status-title.css @@ -18,8 +18,9 @@ [data-slot="tool-status-swap"], [data-slot="tool-status-tail"] { display: inline-grid; - overflow: clip; + overflow: hidden; justify-items: start; + transition: width var(--tool-motion-spring-ms, 480ms) var(--tool-motion-ease, cubic-bezier(0.22, 1, 0.36, 1)); } [data-slot="tool-status-active"], @@ -30,8 +31,8 @@ text-align: start; transition-property: opacity, filter, transform; transition-duration: - var(--tool-motion-fade-ms, 400ms), calc(var(--tool-motion-fade-ms, 400ms) * 0.8), - calc(var(--tool-motion-fade-ms, 400ms) * 0.8); + var(--tool-motion-fade-ms, 240ms), calc(var(--tool-motion-fade-ms, 240ms) * 0.8), + calc(var(--tool-motion-fade-ms, 240ms) * 0.8); transition-timing-function: ease-out, ease-out, ease-out; } diff --git a/packages/ui/src/components/tool-status-title.tsx b/packages/ui/src/components/tool-status-title.tsx index 444955af98..68440b6c63 100644 --- a/packages/ui/src/components/tool-status-title.tsx +++ b/packages/ui/src/components/tool-status-title.tsx @@ -1,8 +1,17 @@ import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js" -import { useReducedMotion } from "../hooks/use-reduced-motion" -import { animate, type AnimationPlaybackControls, GROW_SPRING } from "./motion" import { TextShimmer } from "./text-shimmer" -import { commonPrefix } from "./text-utils" + +function common(active: string, done: string) { + const a = Array.from(active) + const b = Array.from(done) + let i = 0 + while (i < a.length && i < b.length && a[i] === b[i]) i++ + return { + prefix: a.slice(0, i).join(""), + active: a.slice(i).join(""), + done: b.slice(i).join(""), + } +} function contentWidth(el: HTMLSpanElement | undefined) { if (!el) return 0 @@ -18,58 +27,25 @@ export function ToolStatusTitle(props: { class?: string split?: boolean }) { - const reduce = useReducedMotion() - const split = createMemo(() => commonPrefix(props.activeText, props.doneText)) + const split = createMemo(() => common(props.activeText, props.doneText)) const suffix = createMemo( - () => - (props.split ?? true) && split().prefix.length >= 2 && split().aSuffix.length > 0 && split().bSuffix.length > 0, + () => (props.split ?? true) && split().prefix.length >= 2 && split().active.length > 0 && split().done.length > 0, ) const prefixLen = createMemo(() => Array.from(split().prefix).length) - const activeTail = createMemo(() => (suffix() ? split().aSuffix : props.activeText)) - const doneTail = createMemo(() => (suffix() ? split().bSuffix : props.doneText)) + const activeTail = createMemo(() => (suffix() ? split().active : props.activeText)) + const doneTail = createMemo(() => (suffix() ? split().done : props.doneText)) + const [width, setWidth] = createSignal("auto") const [ready, setReady] = createSignal(false) let activeRef: HTMLSpanElement | undefined let doneRef: HTMLSpanElement | undefined - let swapRef: HTMLSpanElement | undefined - let tailRef: HTMLSpanElement | undefined let frame: number | undefined let readyFrame: number | undefined - let widthAnim: AnimationPlaybackControls | undefined - - const node = () => (suffix() ? tailRef : swapRef) - - const setNodeWidth = (width: string) => { - if (swapRef) swapRef.style.width = width - if (tailRef) tailRef.style.width = width - } const measure = () => { const target = props.active ? activeRef : doneRef - const next = contentWidth(target) - if (next <= 0) return - - const ref = node() - if (!ref || !ready() || reduce()) { - widthAnim?.stop() - setNodeWidth(`${next}px`) - return - } - - const prev = Math.max(0, Math.ceil(ref.getBoundingClientRect().width)) - if (Math.abs(next - prev) < 1) { - ref.style.width = `${next}px` - return - } - - ref.style.width = `${prev}px` - widthAnim?.stop() - widthAnim = animate(ref, { width: `${next}px` }, GROW_SPRING) - widthAnim.finished.then(() => { - const el = node() - if (!el) return - el.style.width = `${next}px` - }) + const px = contentWidth(target) + if (px > 0) setWidth(`${px}px`) } const schedule = () => { @@ -114,7 +90,6 @@ export function ToolStatusTitle(props: { onCleanup(() => { if (frame !== undefined) cancelAnimationFrame(frame) if (readyFrame !== undefined) cancelAnimationFrame(readyFrame) - widthAnim?.stop() }) return ( @@ -129,7 +104,7 @@ export function ToolStatusTitle(props: { + @@ -143,7 +118,7 @@ export function ToolStatusTitle(props: { - + diff --git a/packages/ui/src/components/tool-utils.ts b/packages/ui/src/components/tool-utils.ts deleted file mode 100644 index 4d57c626e8..0000000000 --- a/packages/ui/src/components/tool-utils.ts +++ /dev/null @@ -1,336 +0,0 @@ -import type { ToolPart } from "@opencode-ai/sdk/v2" -import { createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js" -import { useReducedMotion } from "../hooks/use-reduced-motion" -import { - animate, - type AnimationPlaybackControls, - clearFadeStyles, - clearMaskStyles, - COLLAPSIBLE_SPRING, - GROW_SPRING, - WIPE_MASK, -} from "./motion" - -export const TEXT_RENDER_THROTTLE_MS = 100 - -export function createThrottledValue(getValue: () => string) { - const [value, setValue] = createSignal(getValue()) - let timeout: ReturnType | undefined - let last = 0 - - createEffect(() => { - const next = getValue() - const now = Date.now() - - const remaining = TEXT_RENDER_THROTTLE_MS - (now - last) - if (remaining <= 0) { - if (timeout) { - clearTimeout(timeout) - timeout = undefined - } - last = now - setValue(next) - return - } - if (timeout) clearTimeout(timeout) - timeout = setTimeout(() => { - last = Date.now() - setValue(next) - timeout = undefined - }, remaining) - }) - - onCleanup(() => { - if (timeout) clearTimeout(timeout) - }) - - return value -} - -export function busy(status: string | undefined) { - return status === "pending" || status === "running" -} - -export function hold(state: () => boolean, wait = 2000) { - const [live, setLive] = createSignal(state()) - let timer: ReturnType | undefined - - createEffect(() => { - if (state()) { - if (timer) clearTimeout(timer) - timer = undefined - setLive(true) - return - } - - if (timer) clearTimeout(timer) - timer = setTimeout(() => { - timer = undefined - setLive(false) - }, wait) - }) - - onCleanup(() => { - if (timer) clearTimeout(timer) - }) - - return live -} - -export function updateScrollMask(el: HTMLElement, fade = 12) { - const { scrollTop, scrollHeight, clientHeight } = el - const overflow = scrollHeight - clientHeight - if (overflow <= 1) { - el.style.maskImage = "" - el.style.webkitMaskImage = "" - return - } - const top = scrollTop > 1 - const bottom = scrollTop < overflow - 1 - const mask = - top && bottom - ? `linear-gradient(to bottom, transparent 0, black ${fade}px, black calc(100% - ${fade}px), transparent 100%)` - : top - ? `linear-gradient(to bottom, transparent 0, black ${fade}px)` - : bottom - ? `linear-gradient(to bottom, black calc(100% - ${fade}px), transparent 100%)` - : "" - el.style.maskImage = mask - el.style.webkitMaskImage = mask -} - -export function useCollapsible(options: { - content: () => HTMLElement | undefined - body: () => HTMLElement | undefined - open: () => boolean - measure?: () => number - onOpen?: () => void -}) { - const reduce = useReducedMotion() - let heightAnim: AnimationPlaybackControls | undefined - let fadeAnim: AnimationPlaybackControls | undefined - let gen = 0 - - createEffect( - on(options.open, (isOpen) => { - const content = options.content() - const body = options.body() - if (!content || !body) return - heightAnim?.stop() - fadeAnim?.stop() - if (reduce()) { - body.style.opacity = "" - body.style.filter = "" - if (isOpen) { - content.style.display = "" - content.style.height = "auto" - options.onOpen?.() - return - } - content.style.height = "0px" - content.style.display = "none" - return - } - const id = ++gen - if (isOpen) { - content.style.display = "" - content.style.height = "0px" - body.style.opacity = "0" - body.style.filter = "blur(2px)" - fadeAnim = animate(body, { opacity: [0, 1], filter: ["blur(2px)", "blur(0px)"] }, COLLAPSIBLE_SPRING) - queueMicrotask(() => { - if (gen !== id) return - const c = options.content() - if (!c) return - const h = options.measure?.() ?? Math.ceil(body.getBoundingClientRect().height) - heightAnim = animate(c, { height: ["0px", `${h}px`] }, COLLAPSIBLE_SPRING) - heightAnim.finished.then( - () => { - if (gen !== id) return - c.style.height = "auto" - options.onOpen?.() - }, - () => {}, - ) - }) - return - } - - const h = content.getBoundingClientRect().height - heightAnim = animate(content, { height: [`${h}px`, "0px"] }, COLLAPSIBLE_SPRING) - fadeAnim = animate(body, { opacity: [1, 0], filter: ["blur(0px)", "blur(2px)"] }, COLLAPSIBLE_SPRING) - heightAnim.finished.then( - () => { - if (gen !== id) return - content.style.display = "none" - }, - () => {}, - ) - }), - ) - - onCleanup(() => { - ++gen - heightAnim?.stop() - fadeAnim?.stop() - }) -} - -export function useContextToolPending(parts: () => ToolPart[], working?: () => boolean) { - const anyRunning = createMemo(() => parts().some((part) => busy(part.state.status))) - const [settled, setSettled] = createSignal(false) - createEffect(() => { - if (!anyRunning() && !working?.()) setSettled(true) - }) - return createMemo(() => !settled() && (!!working?.() || anyRunning())) -} - -export function useRowWipe(opts: { - id: () => string - text: () => string | undefined - ref: () => HTMLElement | undefined - seen: Set -}) { - const reduce = useReducedMotion() - - createEffect(() => { - const id = opts.id() - const txt = opts.text() - const el = opts.ref() - if (!el) return - if (!txt) { - clearFadeStyles(el) - clearMaskStyles(el) - return - } - if (reduce() || typeof window === "undefined") { - clearFadeStyles(el) - clearMaskStyles(el) - return - } - if (opts.seen.has(id)) { - clearFadeStyles(el) - clearMaskStyles(el) - return - } - opts.seen.add(id) - - el.style.maskImage = WIPE_MASK - el.style.webkitMaskImage = WIPE_MASK - el.style.maskSize = "240% 100%" - el.style.webkitMaskSize = "240% 100%" - el.style.maskRepeat = "no-repeat" - el.style.webkitMaskRepeat = "no-repeat" - el.style.maskPosition = "100% 0%" - el.style.webkitMaskPosition = "100% 0%" - el.style.opacity = "0" - el.style.filter = "blur(2px)" - el.style.transform = "translateX(-0.06em)" - - let done = false - const clear = () => { - if (done) return - done = true - clearFadeStyles(el) - clearMaskStyles(el) - } - if (typeof requestAnimationFrame !== "function") { - clear() - return - } - let anim: AnimationPlaybackControls | undefined - let frame: number | undefined = requestAnimationFrame(() => { - frame = undefined - const node = opts.ref() - if (!node) return - anim = animate( - node, - { - opacity: [0, 1], - filter: ["blur(2px)", "blur(0px)"], - transform: ["translateX(-0.06em)", "translateX(0)"], - maskPosition: "0% 0%", - }, - GROW_SPRING, - ) - - anim.finished.catch(() => {}).finally(clear) - }) - - onCleanup(() => { - if (frame !== undefined) { - cancelAnimationFrame(frame) - clear() - } - }) - }) -} - -export function useToolFade( - ref: () => HTMLElement | undefined, - options?: { delay?: number; wipe?: boolean; animate?: boolean }, -) { - let anim: AnimationPlaybackControls | undefined - let frame: number | undefined - const delay = options?.delay ?? 0 - const wipe = options?.wipe ?? false - const active = options?.animate !== false - const reduce = useReducedMotion() - - onMount(() => { - if (!active) return - - const el = ref() - if (!el || typeof window === "undefined") return - if (reduce()) return - - const mask = - wipe && - typeof CSS !== "undefined" && - (CSS.supports("mask-image", "linear-gradient(to right, black, transparent)") || - CSS.supports("-webkit-mask-image", "linear-gradient(to right, black, transparent)")) - - el.style.opacity = "0" - el.style.filter = wipe ? "blur(3px)" : "blur(2px)" - el.style.transform = wipe ? "translateX(-0.06em)" : "translateY(0.04em)" - - if (mask) { - el.style.maskImage = WIPE_MASK - el.style.webkitMaskImage = WIPE_MASK - el.style.maskSize = "240% 100%" - el.style.webkitMaskSize = "240% 100%" - el.style.maskRepeat = "no-repeat" - el.style.webkitMaskRepeat = "no-repeat" - el.style.maskPosition = "100% 0%" - el.style.webkitMaskPosition = "100% 0%" - } - - frame = requestAnimationFrame(() => { - frame = undefined - const node = ref() - if (!node) return - - anim = wipe - ? mask - ? animate( - node, - { opacity: 1, filter: "blur(0px)", transform: "translateX(0)", maskPosition: "0% 0%" }, - { ...GROW_SPRING, delay }, - ) - : animate(node, { opacity: 1, filter: "blur(0px)", transform: "translateX(0)" }, { ...GROW_SPRING, delay }) - : animate(node, { opacity: 1, filter: "blur(0px)", transform: "translateY(0)" }, { ...GROW_SPRING, delay }) - - anim?.finished.then(() => { - const value = ref() - if (!value) return - clearFadeStyles(value) - if (mask) clearMaskStyles(value) - }) - }) - }) - - onCleanup(() => { - if (frame !== undefined) cancelAnimationFrame(frame) - anim?.stop() - }) -} diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx index d36102590b..3dc520c621 100644 --- a/packages/ui/src/hooks/create-auto-scroll.tsx +++ b/packages/ui/src/hooks/create-auto-scroll.tsx @@ -1,8 +1,6 @@ import { createEffect, on, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { createResizeObserver } from "@solid-primitives/resize-observer" -import { animate, type AnimationPlaybackControls } from "motion" -import { FAST_SPRING } from "../components/motion" export interface AutoScrollOptions { working: () => boolean @@ -11,28 +9,13 @@ export interface AutoScrollOptions { bottomThreshold?: number } -const SETTLE_MS = 500 -const AUTO_SCROLL_GRACE_MS = 120 -const AUTO_SCROLL_EPSILON = 0.5 -const MANUAL_ANCHOR_MS = 3000 -const MANUAL_ANCHOR_QUIET_FRAMES = 24 - export function createAutoScroll(options: AutoScrollOptions) { let scroll: HTMLElement | undefined let settling = false let settleTimer: ReturnType | undefined + let autoTimer: ReturnType | undefined let cleanup: (() => void) | undefined - let programmaticUntil = 0 - let scrollAnim: AnimationPlaybackControls | undefined - let hold: - | { - el: HTMLElement - top: number - until: number - quiet: number - frame: number | undefined - } - | undefined + let auto: { top: number; time: number } | undefined const threshold = () => options.bottomThreshold ?? 10 @@ -44,160 +27,77 @@ export function createAutoScroll(options: AutoScrollOptions) { const active = () => options.working() || settling const distanceFromBottom = (el: HTMLElement) => { - // With column-reverse, scrollTop=0 is at the bottom, negative = scrolled up - return Math.abs(el.scrollTop) + return el.scrollHeight - el.clientHeight - el.scrollTop } const canScroll = (el: HTMLElement) => { return el.scrollHeight - el.clientHeight > 1 } - const markProgrammatic = () => { - programmaticUntil = Date.now() + AUTO_SCROLL_GRACE_MS - } - - const clearHold = () => { - const next = hold - if (!next) return - if (next.frame !== undefined) cancelAnimationFrame(next.frame) - hold = undefined - } - - const tickHold = () => { - const next = hold - const el = scroll - if (!next || !el) return false - if (Date.now() > next.until) { - clearHold() - return false + // Browsers can dispatch scroll events asynchronously. If new content arrives + // between us calling `scrollTo()` and the subsequent `scroll` event firing, + // the handler can see a non-zero `distanceFromBottom` and incorrectly assume + // the user scrolled. + const markAuto = (el: HTMLElement) => { + auto = { + top: Math.max(0, el.scrollHeight - el.clientHeight), + time: Date.now(), } - if (!next.el.isConnected) { - clearHold() + + if (autoTimer) clearTimeout(autoTimer) + autoTimer = setTimeout(() => { + auto = undefined + autoTimer = undefined + }, 1500) + } + + const isAuto = (el: HTMLElement) => { + const a = auto + if (!a) return false + + if (Date.now() - a.time > 1500) { + auto = undefined return false } - const current = next.el.getBoundingClientRect().top - if (!Number.isFinite(current)) { - clearHold() - return false - } - - const delta = current - next.top - if (Math.abs(delta) <= AUTO_SCROLL_EPSILON) { - next.quiet += 1 - if (next.quiet > MANUAL_ANCHOR_QUIET_FRAMES) { - clearHold() - return false - } - return true - } - - next.quiet = 0 - if (!store.userScrolled) { - setStore("userScrolled", true) - options.onUserInteracted?.() - } - el.scrollTop += delta - markProgrammatic() - return true + return Math.abs(el.scrollTop - a.top) < 2 } - const scheduleHold = () => { - const next = hold - if (!next) return - if (next.frame !== undefined) return - - next.frame = requestAnimationFrame(() => { - const value = hold - if (!value) return - value.frame = undefined - if (!tickHold()) return - scheduleHold() - }) - } - - const preserve = (target: HTMLElement) => { + const scrollToBottomNow = (behavior: ScrollBehavior) => { const el = scroll if (!el) return - - if (!store.userScrolled) { - setStore("userScrolled", true) - options.onUserInteracted?.() + markAuto(el) + if (behavior === "smooth") { + el.scrollTo({ top: el.scrollHeight, behavior }) + return } - const top = target.getBoundingClientRect().top - if (!Number.isFinite(top)) return - - clearHold() - hold = { - el: target, - top, - until: Date.now() + MANUAL_ANCHOR_MS, - quiet: 0, - frame: undefined, - } - scheduleHold() + // `scrollTop` assignment bypasses any CSS `scroll-behavior: smooth`. + el.scrollTop = el.scrollHeight } const scrollToBottom = (force: boolean) => { if (!force && !active()) return - clearHold() - if (force && store.userScrolled) setStore("userScrolled", false) const el = scroll if (!el) return - if (scrollAnim) cancelSmooth() if (!force && store.userScrolled) return - // With column-reverse, scrollTop=0 is at the bottom - if (Math.abs(el.scrollTop) <= AUTO_SCROLL_EPSILON) { - markProgrammatic() + const distance = distanceFromBottom(el) + if (distance < 2) { + markAuto(el) return } - el.scrollTop = 0 - markProgrammatic() + // For auto-following content we prefer immediate updates to avoid + // visible "catch up" animations while content is still settling. + scrollToBottomNow("auto") } - const cancelSmooth = () => { - if (scrollAnim) { - scrollAnim.stop() - scrollAnim = undefined - } - } - - const smoothScrollToBottom = () => { - const el = scroll - if (!el) return - - cancelSmooth() - if (store.userScrolled) setStore("userScrolled", false) - - // With column-reverse, scrollTop=0 is at the bottom - if (Math.abs(el.scrollTop) <= AUTO_SCROLL_EPSILON) { - markProgrammatic() - return - } - - scrollAnim = animate(el.scrollTop, 0, { - ...FAST_SPRING, - onUpdate: (v) => { - markProgrammatic() - el.scrollTop = v - }, - onComplete: () => { - scrollAnim = undefined - markProgrammatic() - }, - }) - } - - const stop = (input?: { hold?: boolean }) => { - if (input?.hold !== false) clearHold() - + const stop = () => { const el = scroll if (!el) return if (!canScroll(el)) { @@ -206,25 +106,15 @@ export function createAutoScroll(options: AutoScrollOptions) { } if (store.userScrolled) return - markProgrammatic() setStore("userScrolled", true) options.onUserInteracted?.() } const handleWheel = (e: WheelEvent) => { - if (e.deltaY !== 0) clearHold() - - if (e.deltaY > 0) { - const el = scroll - if (!el) return - if (distanceFromBottom(el) >= threshold()) return - if (store.userScrolled) setStore("userScrolled", false) - markProgrammatic() - return - } - if (e.deltaY >= 0) return - cancelSmooth() + // If the user is scrolling within a nested scrollable region (tool output, + // code block, etc), don't treat it as leaving the "follow bottom" mode. + // Those regions opt in via `data-scrollable`. const el = scroll const target = e.target instanceof Element ? e.target : undefined const nested = target?.closest("[data-scrollable]") @@ -236,27 +126,23 @@ export function createAutoScroll(options: AutoScrollOptions) { const el = scroll if (!el) return - if (hold) { - if (Date.now() < programmaticUntil) return - clearHold() - } - if (!canScroll(el)) { if (store.userScrolled) setStore("userScrolled", false) - markProgrammatic() return } if (distanceFromBottom(el) < threshold()) { - if (Date.now() < programmaticUntil) return if (store.userScrolled) setStore("userScrolled", false) - markProgrammatic() return } - if (!store.userScrolled && Date.now() < programmaticUntil) return + // Ignore scroll events triggered by our own scrollToBottom calls. + if (!store.userScrolled && isAuto(el)) { + scrollToBottom(false) + return + } - stop({ hold: false }) + stop() } const handleInteraction = () => { @@ -268,11 +154,6 @@ export function createAutoScroll(options: AutoScrollOptions) { } const updateOverflowAnchor = (el: HTMLElement) => { - if (hold) { - el.style.overflowAnchor = "none" - return - } - const mode = options.overflowAnchor ?? "dynamic" if (mode === "none") { @@ -292,17 +173,15 @@ export function createAutoScroll(options: AutoScrollOptions) { () => store.contentRef, () => { const el = scroll - if (hold) { - scheduleHold() - return - } if (el && !canScroll(el)) { if (store.userScrolled) setStore("userScrolled", false) - markProgrammatic() return } if (!active()) return if (store.userScrolled) return + // ResizeObserver fires after layout, before paint. + // Keep the bottom locked in the same frame to avoid visible + // "jump up then catch up" artifacts while streaming content. scrollToBottom(false) }, ) @@ -321,11 +200,13 @@ export function createAutoScroll(options: AutoScrollOptions) { settling = true settleTimer = setTimeout(() => { settling = false - }, SETTLE_MS) + }, 300) }), ) createEffect(() => { + // Track `userScrolled` even before `scrollRef` is attached, so we can + // update overflow anchoring once the element exists. store.userScrolled const el = scroll if (!el) return @@ -334,8 +215,7 @@ export function createAutoScroll(options: AutoScrollOptions) { onCleanup(() => { if (settleTimer) clearTimeout(settleTimer) - clearHold() - cancelSmooth() + if (autoTimer) clearTimeout(autoTimer) if (cleanup) cleanup() }) @@ -348,12 +228,8 @@ export function createAutoScroll(options: AutoScrollOptions) { scroll = el - if (!el) { - clearHold() - return - } + if (!el) return - markProgrammatic() updateOverflowAnchor(el) el.addEventListener("wheel", handleWheel, { passive: true }) @@ -364,18 +240,13 @@ export function createAutoScroll(options: AutoScrollOptions) { contentRef: (el: HTMLElement | undefined) => setStore("contentRef", el), handleScroll, handleInteraction, - preserve, pause: stop, - forceScrollToBottom: () => scrollToBottom(true), - smoothScrollToBottom, - snapToBottom: () => { - const el = scroll - if (!el) return + resume: () => { if (store.userScrolled) setStore("userScrolled", false) - // With column-reverse, scrollTop=0 is at the bottom - el.scrollTop = 0 - markProgrammatic() + scrollToBottom(true) }, + scrollToBottom: () => scrollToBottom(false), + forceScrollToBottom: () => scrollToBottom(true), userScrolled: () => store.userScrolled, } } diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts index 0fcf6f086c..1c90a2e493 100644 --- a/packages/ui/src/hooks/index.ts +++ b/packages/ui/src/hooks/index.ts @@ -1,3 +1,2 @@ export * from "./use-filtered-list" export * from "./create-auto-scroll" -export * from "./use-reduced-motion" diff --git a/packages/ui/src/hooks/use-reduced-motion.ts b/packages/ui/src/hooks/use-reduced-motion.ts deleted file mode 100644 index 0038760ec8..0000000000 --- a/packages/ui/src/hooks/use-reduced-motion.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { isHydrated } from "@solid-primitives/lifecycle" -import { createMediaQuery } from "@solid-primitives/media" -import { createHydratableSingletonRoot } from "@solid-primitives/rootless" - -const query = "(prefers-reduced-motion: reduce)" - -export const useReducedMotion = createHydratableSingletonRoot(() => { - const value = createMediaQuery(query) - return () => !isHydrated() || value() -}) diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 213a37c514..cec42f5a0c 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -40,7 +40,6 @@ @import "../components/progress-circle.css" layer(components); @import "../components/radio-group.css" layer(components); @import "../components/resize-handle.css" layer(components); -@import "../components/rolling-results.css" layer(components); @import "../components/select.css" layer(components); @import "../components/spinner.css" layer(components); @import "../components/switch.css" layer(components); diff --git a/packages/util/src/array.ts b/packages/util/src/array.ts index 91b923dee2..1fb8ac69ec 100644 --- a/packages/util/src/array.ts +++ b/packages/util/src/array.ts @@ -1,10 +1,3 @@ -export function same(a: readonly T[] | undefined, b: readonly T[] | undefined) { - if (a === b) return true - if (!a || !b) return false - if (a.length !== b.length) return false - return a.every((x, i) => x === b[i]) -} - export function findLast( items: readonly T[], predicate: (item: T, index: number, items: readonly T[]) => boolean, From 9154cd64e7256cadc50cd10334eee8f193a31527 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 9 Mar 2026 12:46:47 +0000 Subject: [PATCH 009/200] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 2f14f9bf4e..73491735f4 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-c99eE1cKAQHvwJosaFo42U9Hk0Rtp/U5oTTlyiz2Zw4=", - "aarch64-linux": "sha256-LbdssPrf8Bijyp4mRo8QaO/swxwUWSo1g0jLPm2rvUA=", - "aarch64-darwin": "sha256-0L9y6Zk4l2vAxsM2bENahhtRZY1C3XhdxLgnnYlhkkY=", - "x86_64-darwin": "sha256-0J5sFG/kHHRDcTpdpdPBMJEOHwCRnAUYmbxEHPPLDvU=" + "x86_64-linux": "sha256-4kjoJ06VNvHltPHfzQRBG0bC6R39jao10ffGzrNZ230=", + "aarch64-linux": "sha256-6Uio+S2rcyBWbBEeOZb9N1CCKgkbKi68lOIKi3Ws/pQ=", + "aarch64-darwin": "sha256-8ngN5KVN4vhdsk0QJ11BGgSVBrcaEbwSj23c77HBpgs=", + "x86_64-darwin": "sha256-v/ueYGb9a0Nymzy+mkO4uQr78DAuJnES1qOT0onFgnQ=" } } From 0243be86a7b548fd6daf421d39bd3c36ca76246b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 07:49:01 -0500 Subject: [PATCH 010/200] fix(app): don't animate review panel in/out --- packages/app/src/pages/session.tsx | 20 ++++++++++++++++++- .../src/pages/session/session-side-panel.tsx | 3 ++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 3f5da0c947..35e89eabe4 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -285,6 +285,7 @@ export default function Page() { const [ui, setUi] = createStore({ git: false, pendingMessage: undefined as string | undefined, + reviewSnap: false, scrollGesture: 0, scroll: { overflow: false, @@ -460,6 +461,21 @@ export default function Page() { return key }, sessionKey()) + let reviewFrame: number | undefined + + createComputed((prev) => { + const open = desktopReviewOpen() + if (prev === undefined || prev === open) return open + + if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame) + setUi("reviewSnap", true) + reviewFrame = requestAnimationFrame(() => { + reviewFrame = undefined + setUi("reviewSnap", false) + }) + return open + }, desktopReviewOpen()) + const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? []) const reviewDiffs = createMemo(() => (store.changes === "session" ? diffs() : turnDiffs())) @@ -1233,6 +1249,7 @@ export default function Page() { onCleanup(() => { document.removeEventListener("keydown", handleKeyDown) scrollSpy.destroy() + if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame) if (scrollStateFrame !== undefined) cancelAnimationFrame(scrollStateFrame) }) @@ -1254,7 +1271,7 @@ export default function Page() { classList={{ "@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger flex-1 md:flex-none": true, "transition-[width] duration-[240ms] ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none": - !size.active(), + !size.active() && !ui.reviewSnap, }} style={{ width: sessionPanelWidth(), @@ -1369,6 +1386,7 @@ export default function Page() { reviewPanel={reviewPanel} activeDiff={tree.activeDiff} focusReviewDiff={focusReviewDiff} + reviewSnap={ui.reviewSnap} size={size} />
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index a5e067c6f0..590f5b6d9b 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -31,6 +31,7 @@ export function SessionSidePanel(props: { reviewPanel: () => JSX.Element activeDiff?: string focusReviewDiff: (path: string) => void + reviewSnap: boolean size: Sizing }) { const params = useParams() @@ -228,7 +229,7 @@ export function SessionSidePanel(props: { classList={{ "pointer-events-none": !open(), "transition-[width] duration-[240ms] ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none": - !props.size.active(), + !props.size.active() && !props.reviewSnap, }} style={{ width: panelWidth() }} > From 5cc61e1b536c047a9ff2b348d99711807c6c270f Mon Sep 17 00:00:00 2001 From: David Hill Date: Mon, 9 Mar 2026 13:04:37 +0000 Subject: [PATCH 011/200] tui: fix sidebar workspace container sizing by adding box-border class to prevent content overflow issues --- packages/app/src/pages/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index b7ac28ae1a..aab16b59de 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1917,7 +1917,7 @@ export default function Layout(props: ParentProps) { return (
Date: Mon, 9 Mar 2026 08:25:34 -0500 Subject: [PATCH 012/200] fix(app): remove oc-1 theme --- packages/app/e2e/settings/settings.spec.ts | 36 +++++++++++++++++ packages/app/public/oc-theme-preload.js | 10 ++++- packages/app/src/theme-preload.test.ts | 46 ++++++++++++++++++++++ packages/ui/src/theme/context.tsx | 45 ++++++++++++++++----- packages/ui/src/theme/default-themes.ts | 3 -- packages/ui/src/theme/index.ts | 1 - packages/ui/src/theme/themes/oc-1.json | 35 ---------------- 7 files changed, 126 insertions(+), 50 deletions(-) create mode 100644 packages/app/src/theme-preload.test.ts delete mode 100644 packages/ui/src/theme/themes/oc-1.json diff --git a/packages/app/e2e/settings/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts index 42fe0b06c9..f25e91a315 100644 --- a/packages/app/e2e/settings/settings.spec.ts +++ b/packages/app/e2e/settings/settings.spec.ts @@ -116,6 +116,42 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) => expect(dataTheme).toBe(storedThemeId) }) +test("legacy oc-1 theme migrates to oc-2", async ({ page, gotoSession }) => { + await page.addInitScript(() => { + localStorage.setItem("opencode-theme-id", "oc-1") + localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;") + localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;") + }) + + await gotoSession() + + await expect(page.locator("html")).toHaveAttribute("data-theme", "oc-2") + + await expect + .poll(async () => { + return await page.evaluate(() => { + return localStorage.getItem("opencode-theme-id") + }) + }) + .toBe("oc-2") + + await expect + .poll(async () => { + return await page.evaluate(() => { + return localStorage.getItem("opencode-theme-css-light") + }) + }) + .toBeNull() + + await expect + .poll(async () => { + return await page.evaluate(() => { + return localStorage.getItem("opencode-theme-css-dark") + }) + }) + .toBeNull() +}) + test("changing font persists in localStorage and updates CSS variable", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/public/oc-theme-preload.js b/packages/app/public/oc-theme-preload.js index 5851f756e5..36fa5d726a 100644 --- a/packages/app/public/oc-theme-preload.js +++ b/packages/app/public/oc-theme-preload.js @@ -1,5 +1,13 @@ ;(function () { - var themeId = localStorage.getItem("opencode-theme-id") || "oc-2" + var key = "opencode-theme-id" + var themeId = localStorage.getItem(key) || "oc-2" + + if (themeId === "oc-1") { + themeId = "oc-2" + localStorage.setItem(key, themeId) + localStorage.removeItem("opencode-theme-css-light") + localStorage.removeItem("opencode-theme-css-dark") + } var scheme = localStorage.getItem("opencode-color-scheme") || "system" var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches) diff --git a/packages/app/src/theme-preload.test.ts b/packages/app/src/theme-preload.test.ts new file mode 100644 index 0000000000..00d7da2394 --- /dev/null +++ b/packages/app/src/theme-preload.test.ts @@ -0,0 +1,46 @@ +import { beforeEach, describe, expect, test } from "bun:test" + +const src = await Bun.file(new URL("../public/oc-theme-preload.js", import.meta.url)).text() + +const run = () => Function(src)() + +beforeEach(() => { + document.head.innerHTML = "" + document.documentElement.removeAttribute("data-theme") + document.documentElement.removeAttribute("data-color-scheme") + localStorage.clear() + Object.defineProperty(window, "matchMedia", { + value: () => + ({ + matches: false, + }) as MediaQueryList, + configurable: true, + }) +}) + +describe("theme preload", () => { + test("migrates legacy oc-1 to oc-2 before mount", () => { + localStorage.setItem("opencode-theme-id", "oc-1") + localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;") + localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;") + + run() + + expect(document.documentElement.dataset.theme).toBe("oc-2") + expect(document.documentElement.dataset.colorScheme).toBe("light") + expect(localStorage.getItem("opencode-theme-id")).toBe("oc-2") + expect(localStorage.getItem("opencode-theme-css-light")).toBeNull() + expect(localStorage.getItem("opencode-theme-css-dark")).toBeNull() + expect(document.getElementById("oc-theme-preload")).toBeNull() + }) + + test("keeps cached css for non-default themes", () => { + localStorage.setItem("opencode-theme-id", "nightowl") + localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;") + + run() + + expect(document.documentElement.dataset.theme).toBe("nightowl") + expect(document.getElementById("oc-theme-preload")?.textContent).toContain("--background-base:#fff;") + }) +}) diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx index 600c6121c3..cda967697c 100644 --- a/packages/ui/src/theme/context.tsx +++ b/packages/ui/src/theme/context.tsx @@ -16,6 +16,15 @@ const STORAGE_KEYS = { const THEME_STYLE_ID = "oc-theme" +function normalize(id: string | null | undefined) { + return id === "oc-1" ? "oc-2" : id +} + +function clear() { + localStorage.removeItem(STORAGE_KEYS.THEME_CSS_LIGHT) + localStorage.removeItem(STORAGE_KEYS.THEME_CSS_DARK) +} + function ensureThemeStyleElement(): HTMLStyleElement { const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null if (existing) return existing @@ -71,7 +80,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ init: (props: { defaultTheme?: string }) => { const [store, setStore] = createStore({ themes: DEFAULT_THEMES as Record, - themeId: props.defaultTheme ?? "oc-2", + themeId: normalize(props.defaultTheme) ?? "oc-2", colorScheme: "system" as ColorScheme, mode: getSystemMode(), previewThemeId: null as string | null, @@ -89,9 +98,14 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ onCleanup(() => mediaQuery.removeEventListener("change", handler)) const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID) + const themeId = normalize(savedTheme) const savedScheme = localStorage.getItem(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null - if (savedTheme && store.themes[savedTheme]) { - setStore("themeId", savedTheme) + if (themeId && store.themes[themeId]) { + setStore("themeId", themeId) + } + if (savedTheme && themeId && savedTheme !== themeId) { + localStorage.setItem(STORAGE_KEYS.THEME_ID, themeId) + clear() } if (savedScheme) { setStore("colorScheme", savedScheme) @@ -113,14 +127,23 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ }) const setTheme = (id: string) => { - const theme = store.themes[id] + const next = normalize(id) + if (!next) { + console.warn(`Theme "${id}" not found`) + return + } + const theme = store.themes[next] if (!theme) { console.warn(`Theme "${id}" not found`) return } - setStore("themeId", id) - localStorage.setItem(STORAGE_KEYS.THEME_ID, id) - cacheThemeVariants(theme, id) + setStore("themeId", next) + localStorage.setItem(STORAGE_KEYS.THEME_ID, next) + if (next === "oc-2") { + clear() + return + } + cacheThemeVariants(theme, next) } const setColorScheme = (scheme: ColorScheme) => { @@ -138,15 +161,17 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ setColorScheme, registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme), previewTheme: (id: string) => { - const theme = store.themes[id] + const next = normalize(id) + if (!next) return + const theme = store.themes[next] if (!theme) return - setStore("previewThemeId", id) + setStore("previewThemeId", next) const previewMode = store.previewScheme ? store.previewScheme === "system" ? getSystemMode() : store.previewScheme : store.mode - applyThemeCss(theme, id, previewMode) + applyThemeCss(theme, next, previewMode) }, previewColorScheme: (scheme: ColorScheme) => { setStore("previewScheme", scheme) diff --git a/packages/ui/src/theme/default-themes.ts b/packages/ui/src/theme/default-themes.ts index 657d21c3cc..b3cfb340c1 100644 --- a/packages/ui/src/theme/default-themes.ts +++ b/packages/ui/src/theme/default-themes.ts @@ -1,5 +1,4 @@ import type { DesktopTheme } from "./types" -import oc1ThemeJson from "./themes/oc-1.json" import oc2ThemeJson from "./themes/oc-2.json" import tokyoThemeJson from "./themes/tokyonight.json" import draculaThemeJson from "./themes/dracula.json" @@ -16,7 +15,6 @@ import carbonfoxThemeJson from "./themes/carbonfox.json" import gruvboxThemeJson from "./themes/gruvbox.json" import auraThemeJson from "./themes/aura.json" -export const oc1Theme = oc1ThemeJson as DesktopTheme export const oc2Theme = oc2ThemeJson as DesktopTheme export const tokyonightTheme = tokyoThemeJson as DesktopTheme export const draculaTheme = draculaThemeJson as DesktopTheme @@ -35,7 +33,6 @@ export const auraTheme = auraThemeJson as DesktopTheme export const DEFAULT_THEMES: Record = { "oc-2": oc2Theme, - "oc-1": oc1Theme, aura: auraTheme, ayu: ayuTheme, carbonfox: carbonfoxTheme, diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts index 1e6fb79324..bfd55e60b8 100644 --- a/packages/ui/src/theme/index.ts +++ b/packages/ui/src/theme/index.ts @@ -35,7 +35,6 @@ export { ThemeProvider, useTheme, type ColorScheme } from "./context" export { DEFAULT_THEMES, - oc1Theme, oc2Theme, tokyonightTheme, draculaTheme, diff --git a/packages/ui/src/theme/themes/oc-1.json b/packages/ui/src/theme/themes/oc-1.json deleted file mode 100644 index 7dec9cb802..0000000000 --- a/packages/ui/src/theme/themes/oc-1.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "https://opencode.ai/desktop-theme.json", - "name": "OC-1", - "id": "oc-1", - "light": { - "palette": { - "neutral": "#8e8b8b", - "ink": "#656363", - "primary": "#dcde8d", - "accent": "#fb4804", - "success": "#12c905", - "warning": "#ffdc17", - "error": "#fc533a", - "info": "#a753ae", - "interactive": "#034cff", - "diffAdd": "#9ff29a", - "diffDelete": "#fc533a" - } - }, - "dark": { - "palette": { - "neutral": "#716c6b", - "ink": "#b7b1b1", - "primary": "#fab283", - "accent": "#ffba92", - "success": "#12c905", - "warning": "#fcd53a", - "error": "#fc533a", - "info": "#edb2f1", - "interactive": "#034cff", - "diffAdd": "#c8ffc4", - "diffDelete": "#fc533a" - } - } -} From 2712244ad3bb6e4bba20952f46a3493e0573dee2 Mon Sep 17 00:00:00 2001 From: opencode Date: Mon, 9 Mar 2026 13:50:43 +0000 Subject: [PATCH 013/200] release: v1.2.23 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 17ea5b347c..ca466664fe 100644 --- a/bun.lock +++ b/bun.lock @@ -26,7 +26,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -76,7 +76,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -110,7 +110,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -137,7 +137,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -161,7 +161,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -185,7 +185,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -218,7 +218,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -248,7 +248,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -277,7 +277,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -293,7 +293,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.2.22", + "version": "1.2.23", "bin": { "opencode": "./bin/opencode", }, @@ -409,7 +409,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -429,7 +429,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.2.22", + "version": "1.2.23", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -440,7 +440,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -475,7 +475,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -521,7 +521,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "zod": "catalog:", }, @@ -532,7 +532,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 51f9883a56..62e4b0b730 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.2.22", + "version": "1.2.23", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 62474711f0..cb068ce5ad 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.2.22", + "version": "1.2.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index b8cf081047..721f0fbf9a 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.2.22", + "version": "1.2.23", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index ed4cfed17a..60992deb2e 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.2.22", + "version": "1.2.23", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index c41e66c051..6b28502c0e 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.2.22", + "version": "1.2.23", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 2b9ce92da4..3a6b8be814 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.2.22", + "version": "1.2.23", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 8663cc8d58..592f6f6a46 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.2.22", + "version": "1.2.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 9807922a2c..8fd76f59cd 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.2.22", + "version": "1.2.23", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index ddc61ca4c6..34d48fd5cf 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.2.22" +version = "1.2.23" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.22/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.22/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.22/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.22/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.22/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 1bbb1dffde..1e703fd760 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.2.22", + "version": "1.2.23", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 7a25100b0b..cf727cea97 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.2.22", + "version": "1.2.23", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index cb6640b5d3..3162a7d344 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.2.22", + "version": "1.2.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 19d50e85cd..9615c7a155 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.2.22", + "version": "1.2.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index b5c02a45ff..9db64eed62 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.2.22", + "version": "1.2.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index ab1f09af3a..ae24225105 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.2.22", + "version": "1.2.23", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 04b0bb93f4..8d037e0ba9 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.2.22", + "version": "1.2.23", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 783b3d1a6f..41318febb7 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.2.22", + "version": "1.2.23", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index dcbbbc3d07..4c0655a882 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.2.22", + "version": "1.2.23", "publisher": "sst-dev", "repository": { "type": "git", From 5838b58913ec5e6c694c2f8f9df8009f49bf4018 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 9 Mar 2026 22:07:12 +0800 Subject: [PATCH 014/200] add copilot gpt-5.4 xhigh support (#16294) --- packages/opencode/src/provider/transform.ts | 4 ++- .../opencode/test/provider/transform.test.ts | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 6980be0518..471da03cbc 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -440,7 +440,9 @@ export namespace ProviderTransform { const copilotEfforts = iife(() => { if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"] - return WIDELY_SUPPORTED_EFFORTS + const arr = [...WIDELY_SUPPORTED_EFFORTS] + if (id.includes("gpt-5") && model.release_date >= "2025-12-04") arr.push("xhigh") + return arr }) return Object.fromEntries( copilotEfforts.map((effort) => [ diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 2329846351..512819a657 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -2002,6 +2002,35 @@ describe("ProviderTransform.variants", () => { const result = ProviderTransform.variants(model) expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"]) }) + + test("gpt-5.3-codex includes xhigh", () => { + const model = createMockModel({ + id: "gpt-5.3-codex", + providerID: "github-copilot", + api: { + id: "gpt-5.3-codex", + url: "https://api.githubcopilot.com", + npm: "@ai-sdk/github-copilot", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"]) + }) + + test("gpt-5.4 includes xhigh", () => { + const model = createMockModel({ + id: "gpt-5.4", + release_date: "2026-03-05", + providerID: "github-copilot", + api: { + id: "gpt-5.4", + url: "https://api.githubcopilot.com", + npm: "@ai-sdk/github-copilot", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh"]) + }) }) describe("@ai-sdk/cerebras", () => { From ef9bc4ec9ee5101b7f6e584a80600136007ac7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armin=20Pa=C5=A1ali=C4=87?= Date: Mon, 9 Mar 2026 15:22:00 +0100 Subject: [PATCH 015/200] feat(gitlab): send context-1m-2025-08-07 beta header to enable 1M context window (#16153) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/provider/provider.ts | 1 + .../opencode/test/provider/gitlab-duo.test.ts | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 6d5c9d1ad3..09035d272c 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -480,6 +480,7 @@ export namespace Provider { const aiGatewayHeaders = { "User-Agent": `opencode/${Installation.VERSION} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, + "anthropic-beta": "context-1m-2025-08-07", ...(providerConfig?.options?.aiGatewayHeaders || {}), } diff --git a/packages/opencode/test/provider/gitlab-duo.test.ts b/packages/opencode/test/provider/gitlab-duo.test.ts index c512a45909..86e08a7928 100644 --- a/packages/opencode/test/provider/gitlab-duo.test.ts +++ b/packages/opencode/test/provider/gitlab-duo.test.ts @@ -198,6 +198,30 @@ test("GitLab Duo: config apiKey takes precedence over environment variable", asy }) }) +test("GitLab Duo: includes context-1m beta header in aiGatewayHeaders", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("GITLAB_TOKEN", "test-token") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["gitlab"]).toBeDefined() + expect(providers["gitlab"].options?.aiGatewayHeaders?.["anthropic-beta"]).toContain("context-1m-2025-08-07") + }, + }) +}) + test("GitLab Duo: supports feature flags configuration", async () => { await using tmp = await tmpdir({ init: async (dir) => { From 366b8a8034ef7f613e8b93cd872f13c71b836721 Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 9 Mar 2026 10:28:04 -0400 Subject: [PATCH 016/200] feat(tui): add initial support for workspaces into the tui (#16230) --- packages/opencode/src/cli/cmd/tui/app.tsx | 17 + .../tui/component/dialog-workspace-list.tsx | 326 ++++++++++++++++++ .../workspace/dialog-session-list.tsx | 151 ++++++++ .../opencode/src/cli/cmd/tui/context/sdk.tsx | 88 +++-- .../opencode/src/cli/cmd/tui/context/sync.tsx | 16 + .../src/cli/cmd/tui/routes/session/header.tsx | 45 ++- .../src/cli/cmd/tui/routes/session/index.tsx | 6 + packages/opencode/src/cli/cmd/tui/thread.ts | 3 + packages/opencode/src/cli/cmd/tui/worker.ts | 10 +- packages/opencode/src/flag/flag.ts | 2 + packages/sdk/js/src/v2/client.ts | 9 +- 11 files changed, 633 insertions(+), 40 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx create mode 100644 packages/opencode/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 3304d6be6a..e939b831d5 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -20,6 +20,7 @@ import { DialogHelp } from "./ui/dialog-help" import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command" import { DialogAgent } from "@tui/component/dialog-agent" import { DialogSessionList } from "@tui/component/dialog-session-list" +import { DialogWorkspaceList } from "@tui/component/dialog-workspace-list" import { KeybindProvider } from "@tui/context/keybind" import { ThemeProvider, useTheme } from "@tui/context/theme" import { Home } from "@tui/routes/home" @@ -371,6 +372,22 @@ function App() { dialog.replace(() => ) }, }, + ...(Flag.OPENCODE_EXPERIMENTAL_WORKSPACES_TUI + ? [ + { + title: "Manage workspaces", + value: "workspace.list", + category: "Workspace", + suggested: true, + slash: { + name: "workspaces", + }, + onSelect: () => { + dialog.replace(() => ) + }, + }, + ] + : []), { title: "New session", suggested: route.data.type === "session", diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx new file mode 100644 index 0000000000..a25b20505c --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx @@ -0,0 +1,326 @@ +import { useDialog } from "@tui/ui/dialog" +import { DialogSelect } from "@tui/ui/dialog-select" +import { useRoute } from "@tui/context/route" +import { useSync } from "@tui/context/sync" +import { createEffect, createMemo, createSignal, onMount } from "solid-js" +import type { Session } from "@opencode-ai/sdk/v2" +import { useSDK } from "../context/sdk" +import { useToast } from "../ui/toast" +import { useKeybind } from "../context/keybind" +import { DialogSessionList } from "./workspace/dialog-session-list" +import { createOpencodeClient } from "@opencode-ai/sdk/v2" + +async function openWorkspace(input: { + dialog: ReturnType + route: ReturnType + sdk: ReturnType + sync: ReturnType + toast: ReturnType + workspaceID: string + forceCreate?: boolean +}) { + const cacheSession = (session: Session) => { + input.sync.set( + "session", + [...input.sync.data.session.filter((item) => item.id !== session.id), session].toSorted((a, b) => + a.id.localeCompare(b.id), + ), + ) + } + + const client = createOpencodeClient({ + baseUrl: input.sdk.url, + fetch: input.sdk.fetch, + directory: input.sync.data.path.directory || input.sdk.directory, + experimental_workspaceID: input.workspaceID, + }) + const listed = input.forceCreate ? undefined : await client.session.list({ roots: true, limit: 1 }) + const session = listed?.data?.[0] + if (session?.id) { + cacheSession(session) + input.route.navigate({ + type: "session", + sessionID: session.id, + }) + input.dialog.clear() + return + } + let created: Session | undefined + while (!created) { + const result = await client.session.create({}).catch(() => undefined) + if (!result) { + input.toast.show({ + message: "Failed to open workspace", + variant: "error", + }) + return + } + if (result.response.status >= 500 && result.response.status < 600) { + await Bun.sleep(1000) + continue + } + if (!result.data) { + input.toast.show({ + message: "Failed to open workspace", + variant: "error", + }) + return + } + created = result.data + } + cacheSession(created) + input.route.navigate({ + type: "session", + sessionID: created.id, + }) + input.dialog.clear() +} + +function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) => Promise }) { + const dialog = useDialog() + const sync = useSync() + const sdk = useSDK() + const toast = useToast() + const [creating, setCreating] = createSignal() + + onMount(() => { + dialog.setSize("medium") + }) + + const options = createMemo(() => { + const type = creating() + if (type) { + return [ + { + title: `Creating ${type} workspace...`, + value: "creating" as const, + description: "This can take a while for remote environments", + }, + ] + } + return [ + { + title: "Worktree", + value: "worktree" as const, + description: "Create a local git worktree", + }, + ] + }) + + const createWorkspace = async (type: string) => { + if (creating()) return + setCreating(type) + + const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch((err) => { + console.log(err) + return undefined + }) + console.log(JSON.stringify(result, null, 2)) + const workspace = result?.data + if (!workspace) { + setCreating(undefined) + toast.show({ + message: "Failed to create workspace", + variant: "error", + }) + return + } + await sync.workspace.sync() + await props.onSelect(workspace.id) + setCreating(undefined) + } + + return ( + { + if (option.value === "creating") return + void createWorkspace(option.value) + }} + /> + ) +} + +export function DialogWorkspaceList() { + const dialog = useDialog() + const route = useRoute() + const sync = useSync() + const sdk = useSDK() + const toast = useToast() + const keybind = useKeybind() + const [toDelete, setToDelete] = createSignal() + const [counts, setCounts] = createSignal>({}) + + const open = (workspaceID: string, forceCreate?: boolean) => + openWorkspace({ + dialog, + route, + sdk, + sync, + toast, + workspaceID, + forceCreate, + }) + + async function selectWorkspace(workspaceID: string) { + if (workspaceID === "__local__") { + if (localCount() > 0) { + dialog.replace(() => ) + return + } + route.navigate({ + type: "home", + }) + dialog.clear() + return + } + const count = counts()[workspaceID] + if (count && count > 0) { + dialog.replace(() => ) + return + } + + if (count === 0) { + await open(workspaceID) + return + } + const client = createOpencodeClient({ + baseUrl: sdk.url, + fetch: sdk.fetch, + directory: sync.data.path.directory || sdk.directory, + experimental_workspaceID: workspaceID, + }) + const listed = await client.session.list({ roots: true, limit: 1 }).catch(() => undefined) + if (listed?.data?.length) { + dialog.replace(() => ) + return + } + await open(workspaceID) + } + + const currentWorkspaceID = createMemo(() => { + if (route.data.type === "session") { + return sync.session.get(route.data.sessionID)?.workspaceID ?? "__local__" + } + return "__local__" + }) + + const localCount = createMemo( + () => sync.data.session.filter((session) => !session.workspaceID && !session.parentID).length, + ) + + let run = 0 + createEffect(() => { + const workspaces = sync.data.workspaceList + const next = ++run + if (!workspaces.length) { + setCounts({}) + return + } + setCounts(Object.fromEntries(workspaces.map((workspace) => [workspace.id, undefined]))) + void Promise.all( + workspaces.map(async (workspace) => { + const client = createOpencodeClient({ + baseUrl: sdk.url, + fetch: sdk.fetch, + directory: sync.data.path.directory || sdk.directory, + experimental_workspaceID: workspace.id, + }) + const result = await client.session.list({ roots: true }).catch(() => undefined) + return [workspace.id, result ? (result.data?.length ?? 0) : null] as const + }), + ).then((entries) => { + if (run !== next) return + setCounts(Object.fromEntries(entries)) + }) + }) + + const options = createMemo(() => [ + { + title: "Local", + value: "__local__", + category: "Workspace", + description: "Use the local machine", + footer: `${localCount()} session${localCount() === 1 ? "" : "s"}`, + }, + ...sync.data.workspaceList.map((workspace) => { + const count = counts()[workspace.id] + return { + title: + toDelete() === workspace.id + ? `Delete ${workspace.id}? Press ${keybind.print("session_delete")} again` + : workspace.id, + value: workspace.id, + category: workspace.type, + description: workspace.branch ? `Branch ${workspace.branch}` : undefined, + footer: + count === undefined + ? "Loading sessions..." + : count === null + ? "Sessions unavailable" + : `${count} session${count === 1 ? "" : "s"}`, + } + }), + { + title: "+ New workspace", + value: "__create__", + category: "Actions", + description: "Create a new workspace", + }, + ]) + + onMount(() => { + dialog.setSize("large") + void sync.workspace.sync() + }) + + return ( + { + setToDelete(undefined) + }} + onSelect={(option) => { + setToDelete(undefined) + if (option.value === "__create__") { + dialog.replace(() => open(workspaceID, true)} />) + return + } + void selectWorkspace(option.value) + }} + keybind={[ + { + keybind: keybind.all.session_delete?.[0], + title: "delete", + onTrigger: async (option) => { + if (option.value === "__create__" || option.value === "__local__") return + if (toDelete() !== option.value) { + setToDelete(option.value) + return + } + const result = await sdk.client.experimental.workspace.remove({ id: option.value }).catch(() => undefined) + setToDelete(undefined) + if (result?.error) { + toast.show({ + message: "Failed to delete workspace", + variant: "error", + }) + return + } + if (currentWorkspaceID() === option.value) { + route.navigate({ + type: "home", + }) + } + await sync.workspace.sync() + }, + }, + ]} + /> + ) +} diff --git a/packages/opencode/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx new file mode 100644 index 0000000000..326f094a56 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx @@ -0,0 +1,151 @@ +import { useDialog } from "@tui/ui/dialog" +import { DialogSelect } from "@tui/ui/dialog-select" +import { useRoute } from "@tui/context/route" +import { useSync } from "@tui/context/sync" +import { createMemo, createSignal, createResource, onMount, Show } from "solid-js" +import { Locale } from "@/util/locale" +import { useKeybind } from "../../context/keybind" +import { useTheme } from "../../context/theme" +import { useSDK } from "../../context/sdk" +import { DialogSessionRename } from "../dialog-session-rename" +import { useKV } from "../../context/kv" +import { createDebouncedSignal } from "../../util/signal" +import { Spinner } from "../spinner" +import { useToast } from "../../ui/toast" + +export function DialogSessionList(props: { workspaceID?: string; localOnly?: boolean } = {}) { + const dialog = useDialog() + const route = useRoute() + const sync = useSync() + const keybind = useKeybind() + const { theme } = useTheme() + const sdk = useSDK() + const kv = useKV() + const toast = useToast() + const [toDelete, setToDelete] = createSignal() + const [search, setSearch] = createDebouncedSignal("", 150) + + const [listed, listedActions] = createResource( + () => props.workspaceID, + async (workspaceID) => { + if (!workspaceID) return undefined + const result = await sdk.client.session.list({ roots: true }) + return result.data ?? [] + }, + ) + + const [searchResults] = createResource(search, async (query) => { + if (!query || props.localOnly) return undefined + const result = await sdk.client.session.list({ + search: query, + limit: 30, + ...(props.workspaceID ? { roots: true } : {}), + }) + return result.data ?? [] + }) + + const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) + + const sessions = createMemo(() => { + if (searchResults()) return searchResults()! + if (props.workspaceID) return listed() ?? [] + if (props.localOnly) return sync.data.session.filter((session) => !session.workspaceID) + return sync.data.session + }) + + const options = createMemo(() => { + const today = new Date().toDateString() + return sessions() + .filter((x) => { + if (x.parentID !== undefined) return false + if (props.workspaceID && listed()) return true + if (props.workspaceID) return x.workspaceID === props.workspaceID + if (props.localOnly) return !x.workspaceID + return true + }) + .toSorted((a, b) => b.time.updated - a.time.updated) + .map((x) => { + const date = new Date(x.time.updated) + let category = date.toDateString() + if (category === today) { + category = "Today" + } + const isDeleting = toDelete() === x.id + const status = sync.data.session_status?.[x.id] + const isWorking = status?.type === "busy" + return { + title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title, + bg: isDeleting ? theme.error : undefined, + value: x.id, + category, + footer: Locale.time(x.time.updated), + gutter: isWorking ? : undefined, + } + }) + }) + + onMount(() => { + dialog.setSize("large") + }) + + return ( + { + setToDelete(undefined) + }} + onSelect={(option) => { + route.navigate({ + type: "session", + sessionID: option.value, + }) + dialog.clear() + }} + keybind={[ + { + keybind: keybind.all.session_delete?.[0], + title: "delete", + onTrigger: async (option) => { + if (toDelete() === option.value) { + const deleted = await sdk.client.session + .delete({ + sessionID: option.value, + }) + .then(() => true) + .catch(() => false) + setToDelete(undefined) + if (!deleted) { + toast.show({ + message: "Failed to delete session", + variant: "error", + }) + return + } + if (props.workspaceID) { + listedActions.mutate((sessions) => sessions?.filter((session) => session.id !== option.value)) + return + } + sync.set( + "session", + sync.data.session.filter((session) => session.id !== option.value), + ) + return + } + setToDelete(option.value) + }, + }, + { + keybind: keybind.all.session_rename?.[0], + title: "rename", + onTrigger: async (option) => { + dialog.replace(() => ) + }, + }, + ]} + /> + ) +} diff --git a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx index 7fa7e05c3d..2403a4e938 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx @@ -5,6 +5,7 @@ import { batch, onCleanup, onMount } from "solid-js" export type EventSource = { on: (handler: (event: Event) => void) => () => void + setWorkspace?: (workspaceID?: string) => void } export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ @@ -17,13 +18,21 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ events?: EventSource }) => { const abort = new AbortController() - const sdk = createOpencodeClient({ - baseUrl: props.url, - signal: abort.signal, - directory: props.directory, - fetch: props.fetch, - headers: props.headers, - }) + let workspaceID: string | undefined + let sse: AbortController | undefined + + function createSDK() { + return createOpencodeClient({ + baseUrl: props.url, + signal: abort.signal, + directory: props.directory, + fetch: props.fetch, + headers: props.headers, + experimental_workspaceID: workspaceID, + }) + } + + let sdk = createSDK() const emitter = createGlobalEmitter<{ [key in Event["type"]]: Extract @@ -61,41 +70,56 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ flush() } - onMount(async () => { - // If an event source is provided, use it instead of SSE + function startSSE() { + sse?.abort() + const ctrl = new AbortController() + sse = ctrl + ;(async () => { + while (true) { + if (abort.signal.aborted || ctrl.signal.aborted) break + const events = await sdk.event.subscribe({}, { signal: ctrl.signal }) + + for await (const event of events.stream) { + if (ctrl.signal.aborted) break + handleEvent(event) + } + + if (timer) clearTimeout(timer) + if (queue.length > 0) flush() + } + })().catch(() => {}) + } + + onMount(() => { if (props.events) { const unsub = props.events.on(handleEvent) onCleanup(unsub) - return - } - - // Fall back to SSE - while (true) { - if (abort.signal.aborted) break - const events = await sdk.event.subscribe( - {}, - { - signal: abort.signal, - }, - ) - - for await (const event of events.stream) { - handleEvent(event) - } - - // Flush any remaining events - if (timer) clearTimeout(timer) - if (queue.length > 0) { - flush() - } + } else { + startSSE() } }) onCleanup(() => { abort.abort() + sse?.abort() if (timer) clearTimeout(timer) }) - return { client: sdk, event: emitter, url: props.url } + return { + get client() { + return sdk + }, + directory: props.directory, + event: emitter, + fetch: props.fetch ?? fetch, + setWorkspace(next?: string) { + if (workspaceID === next) return + workspaceID = next + sdk = createSDK() + props.events?.setWorkspace?.(next) + if (!props.events) startSSE() + }, + url: props.url, + } }, }) diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 269ed7ae0b..3b296a927a 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -28,6 +28,7 @@ import { useArgs } from "./args" import { batch, onMount } from "solid-js" import { Log } from "@/util/log" import type { Path } from "@opencode-ai/sdk" +import type { Workspace } from "@opencode-ai/sdk/v2" export const { use: useSync, provider: SyncProvider } = createSimpleContext({ name: "Sync", @@ -73,6 +74,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ formatter: FormatterStatus[] vcs: VcsInfo | undefined path: Path + workspaceList: Workspace[] }>({ provider_next: { all: [], @@ -100,10 +102,17 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ formatter: [], vcs: undefined, path: { state: "", config: "", worktree: "", directory: "" }, + workspaceList: [], }) const sdk = useSDK() + async function syncWorkspaces() { + const result = await sdk.client.experimental.workspace.list().catch(() => undefined) + if (!result?.data) return + setStore("workspaceList", reconcile(result.data)) + } + sdk.event.listen((e) => { const event = e.details switch (event.type) { @@ -413,6 +422,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ sdk.client.provider.auth().then((x) => setStore("provider_auth", reconcile(x.data ?? {}))), sdk.client.vcs.get().then((x) => setStore("vcs", reconcile(x.data))), sdk.client.path.get().then((x) => setStore("path", reconcile(x.data!))), + syncWorkspaces(), ]).then(() => { setStore("status", "complete") }) @@ -481,6 +491,12 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ fullSyncedSessions.add(sessionID) }, }, + workspace: { + get(workspaceID: string) { + return store.workspaceList.find((workspace) => workspace.id === workspaceID) + }, + sync: syncWorkspaces, + }, bootstrap, } return result diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx index 0c5ea9a857..49b2d61091 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx @@ -7,6 +7,7 @@ import { SplitBorder } from "@tui/component/border" import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2" import { useCommandDialog } from "@tui/component/dialog-command" import { useKeybind } from "../../context/keybind" +import { Flag } from "@/flag/flag" import { useTerminalDimensions } from "@opentui/solid" const Title = (props: { session: Accessor }) => { @@ -29,6 +30,17 @@ const ContextInfo = (props: { context: Accessor; cost: Acces ) } +const WorkspaceInfo = (props: { workspace: Accessor }) => { + const { theme } = useTheme() + return ( + + + {props.workspace()} + + + ) +} + export function Header() { const route = useRouteData("session") const sync = useSync() @@ -59,6 +71,14 @@ export function Header() { return result }) + const workspace = createMemo(() => { + const id = session()?.workspaceID + if (!id) return "Workspace local" + const info = sync.workspace.get(id) + if (!info) return `Workspace ${id}` + return `Workspace ${id} (${info.type})` + }) + const { theme } = useTheme() const keybind = useKeybind() const command = useCommandDialog() @@ -83,9 +103,19 @@ export function Header() { - - Subagent session - + {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES_TUI ? ( + + + Subagent session + + + + ) : ( + + Subagent session + + )} + @@ -124,7 +154,14 @@ export function Header() { - + {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES_TUI ? ( + <box flexDirection="column"> + <Title session={session} /> + <WorkspaceInfo workspace={workspace} /> + </box> + ) : ( + <Title session={session} /> + )} <ContextInfo context={context} cost={cost} /> </box> </Match> diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index d3a4ff81e0..5358b61ef3 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -182,6 +182,12 @@ export function Session() { return new CustomSpeedScroll(3) }) + createEffect(() => { + if (session()?.workspaceID) { + sdk.setWorkspace(session()?.workspaceID) + } + }) + createEffect(async () => { await sync.session .sync(route.sessionID) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 14a9c88731..6e787c7afd 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -42,6 +42,9 @@ function createWorkerFetch(client: RpcClient): typeof fetch { function createEventSource(client: RpcClient): EventSource { return { on: (handler) => client.on<Event>("event", handler), + setWorkspace: (workspaceID) => { + void client.call("setWorkspace", { workspaceID }) + }, } } diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 4452d6d764..f8dcee78a0 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -44,7 +44,7 @@ const eventStream = { abort: undefined as AbortController | undefined, } -const startEventStream = (directory: string) => { +const startEventStream = (input: { directory: string; workspaceID?: string }) => { if (eventStream.abort) eventStream.abort.abort() const abort = new AbortController() eventStream.abort = abort @@ -59,7 +59,8 @@ const startEventStream = (directory: string) => { const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", - directory, + directory: input.directory, + experimental_workspaceID: input.workspaceID, fetch: fetchFn, signal, }) @@ -95,7 +96,7 @@ const startEventStream = (directory: string) => { }) } -startEventStream(process.cwd()) +startEventStream({ directory: process.cwd() }) export const rpc = { async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) { @@ -135,6 +136,9 @@ export const rpc = { Config.global.reset() await Instance.disposeAll() }, + async setWorkspace(input: { workspaceID?: string }) { + startEventStream({ directory: process.cwd(), workspaceID: input.workspaceID }) + }, async shutdown() { Log.Default.info("worker shutting down") if (eventStream.abort) eventStream.abort.abort() diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 6b2b16c67b..7c48e99f47 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -57,6 +57,8 @@ export namespace Flag { export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL") export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK") export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE") + export const OPENCODE_EXPERIMENTAL_WORKSPACES_TUI = + OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES_TUI") export const OPENCODE_EXPERIMENTAL_MARKDOWN = !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN") export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"] export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"] diff --git a/packages/sdk/js/src/v2/client.ts b/packages/sdk/js/src/v2/client.ts index 8685be52d6..ad956dd4b3 100644 --- a/packages/sdk/js/src/v2/client.ts +++ b/packages/sdk/js/src/v2/client.ts @@ -5,7 +5,7 @@ import { type Config } from "./gen/client/types.gen.js" import { OpencodeClient } from "./gen/sdk.gen.js" export { type Config as OpencodeClientConfig, OpencodeClient } -export function createOpencodeClient(config?: Config & { directory?: string }) { +export function createOpencodeClient(config?: Config & { directory?: string; experimental_workspaceID?: string }) { if (!config?.fetch) { const customFetch: any = (req: any) => { // @ts-ignore @@ -27,6 +27,13 @@ export function createOpencodeClient(config?: Config & { directory?: string }) { } } + if (config?.experimental_workspaceID) { + config.headers = { + ...config.headers, + "x-opencode-workspace": config.experimental_workspaceID, + } + } + const client = createClient(config) return new OpencodeClient({ client }) } From 0388ec686249b6a6a22b7156d0cc87adfaf423bf Mon Sep 17 00:00:00 2001 From: Karan Handa <79533543+ShriKaranHanda@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:03:19 +0530 Subject: [PATCH 017/200] fix(storybook): add ci build workflow (#16760) --- .github/workflows/storybook.yml | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/storybook.yml diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml new file mode 100644 index 0000000000..6d143a8a22 --- /dev/null +++ b/.github/workflows/storybook.yml @@ -0,0 +1,38 @@ +name: storybook + +on: + push: + branches: [dev] + paths: + - ".github/workflows/storybook.yml" + - "package.json" + - "bun.lock" + - "packages/storybook/**" + - "packages/ui/**" + pull_request: + branches: [dev] + paths: + - ".github/workflows/storybook.yml" + - "package.json" + - "bun.lock" + - "packages/storybook/**" + - "packages/ui/**" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: storybook build + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Build Storybook + run: bun --cwd packages/storybook build From 3742e42fdf6e30573153c698572cbdd291df6e6d Mon Sep 17 00:00:00 2001 From: Filip <34747899+neriousy@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:36:57 +0100 Subject: [PATCH 018/200] =?UTF-8?q?fix(app):=20dismiss=20toast=20notificat?= =?UTF-8?q?ions=20when=20questions=20or=20permissions=20a=E2=80=A6=20(#167?= =?UTF-8?q?58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/app/src/pages/layout.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index aab16b59de..40015db1be 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -424,6 +424,17 @@ export default function Layout(props: ParentProps) { return } + if ( + e.details?.type === "question.replied" || + e.details?.type === "question.rejected" || + e.details?.type === "permission.replied" + ) { + const props = e.details.properties as { sessionID: string } + const sessionKey = `${e.name}:${props.sessionID}` + dismissSessionAlert(sessionKey) + return + } + if (e.details?.type !== "permission.asked" && e.details?.type !== "question.asked") return const title = e.details.type === "permission.asked" From 399b8f0701f04ded3114e81cd74e6024bef1a50d Mon Sep 17 00:00:00 2001 From: David Hill <1879069+iamdavidhill@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:46:15 +0000 Subject: [PATCH 019/200] fix(app): session title turn spinner (#16764) --- .../src/pages/session/message-timeline.tsx | 126 +++++++++++++----- packages/ui/src/components/spinner.tsx | 1 + packages/ui/src/styles/animations.css | 4 +- 3 files changed, 94 insertions(+), 37 deletions(-) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index ce6a01378c..4060e5e5c2 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -8,6 +8,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Dialog } from "@opencode-ai/ui/dialog" import { InlineInput } from "@opencode-ai/ui/inline-input" +import { Spinner } from "@opencode-ai/ui/spinner" import { SessionTurn } from "@opencode-ai/ui/session-turn" import { ScrollView } from "@opencode-ai/ui/scroll-view" import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2" @@ -235,6 +236,40 @@ export function MessageTimeline(props: { if (!id) return idle return sync.data.session_status[id] ?? idle }) + const working = createMemo(() => !!pending() || sessionStatus().type !== "idle") + + const [slot, setSlot] = createStore({ + open: false, + show: false, + fade: false, + }) + + let f: number | undefined + const clear = () => { + if (f !== undefined) window.clearTimeout(f) + f = undefined + } + + onCleanup(clear) + createEffect( + on( + working, + (on, prev) => { + clear() + if (on) { + setSlot({ open: true, show: true, fade: false }) + return + } + if (prev) { + setSlot({ open: false, show: true, fade: true }) + f = window.setTimeout(() => setSlot({ show: false, fade: false }), 260) + return + } + setSlot({ open: false, show: false, fade: false }) + }, + { defer: true }, + ), + ) const activeMessageID = createMemo(() => { const parentID = pending()?.parentID if (parentID) { @@ -573,43 +608,64 @@ export function MessageTimeline(props: { aria-label={language.t("common.goBack")} /> </Show> - <Show when={titleValue() || title.editing}> - <Show - when={title.editing} - fallback={ - <h1 - class="text-14-medium text-text-strong truncate grow-1 min-w-0 pl-2" - onDblClick={openTitleEditor} - > - {titleValue()} - </h1> - } + <div class="flex items-center min-w-0 grow-1"> + <div + class="shrink-0 flex items-center justify-center overflow-hidden transition-[width,margin] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]" + style={{ + width: slot.open ? "16px" : "0px", + "margin-right": slot.open ? "8px" : "0px", + }} + aria-hidden="true" > - <InlineInput - ref={(el) => { - titleRef = el - }} - value={title.draft} - disabled={title.saving} - class="text-14-medium text-text-strong grow-1 min-w-0 pl-2 rounded-[6px]" - style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }} - onInput={(event) => setTitle("draft", event.currentTarget.value)} - onKeyDown={(event) => { - event.stopPropagation() - if (event.key === "Enter") { - event.preventDefault() - void saveTitleEditor() - return - } - if (event.key === "Escape") { - event.preventDefault() - closeTitleEditor() - } - }} - onBlur={closeTitleEditor} - /> + <Show when={slot.show}> + <div + class="transition-opacity duration-200 ease-out" + classList={{ + "opacity-0": slot.fade, + }} + > + <Spinner class="size-4" style={{ color: "var(--icon-interactive-base)" }} /> + </div> + </Show> + </div> + <Show when={titleValue() || title.editing}> + <Show + when={title.editing} + fallback={ + <h1 + class="text-14-medium text-text-strong truncate grow-1 min-w-0" + onDblClick={openTitleEditor} + > + {titleValue()} + </h1> + } + > + <InlineInput + ref={(el) => { + titleRef = el + }} + value={title.draft} + disabled={title.saving} + class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px]" + style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }} + onInput={(event) => setTitle("draft", event.currentTarget.value)} + onKeyDown={(event) => { + event.stopPropagation() + if (event.key === "Enter") { + event.preventDefault() + void saveTitleEditor() + return + } + if (event.key === "Escape") { + event.preventDefault() + closeTitleEditor() + } + }} + onBlur={closeTitleEditor} + /> + </Show> </Show> - </Show> + </div> </div> <Show when={sessionID()}> {(id) => ( diff --git a/packages/ui/src/components/spinner.tsx b/packages/ui/src/components/spinner.tsx index cc4149d174..3d029d9765 100644 --- a/packages/ui/src/components/spinner.tsx +++ b/packages/ui/src/components/spinner.tsx @@ -41,6 +41,7 @@ export function Spinner(props: { animation: square.corner ? undefined : `${square.outer ? "pulse-opacity-dim" : "pulse-opacity"} ${square.duration}s ease-in-out infinite`, + "animation-fill-mode": square.corner ? undefined : "both", "animation-delay": square.corner ? undefined : `${square.delay}s`, }} /> diff --git a/packages/ui/src/styles/animations.css b/packages/ui/src/styles/animations.css index f8d11e0e50..f9a09df379 100644 --- a/packages/ui/src/styles/animations.css +++ b/packages/ui/src/styles/animations.css @@ -26,10 +26,10 @@ @keyframes pulse-opacity-dim { 0%, 100% { - opacity: 0; + opacity: 0.15; } 50% { - opacity: 0.2; + opacity: 0.35; } } From 8a51cbd25381768be438696d8209eaaef6a6d41e Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Mon, 9 Mar 2026 11:25:58 -0400 Subject: [PATCH 020/200] core: prevent accidental edits to migration files by restricting agent access --- .opencode/opencode.jsonc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.opencode/opencode.jsonc b/.opencode/opencode.jsonc index 3497847a67..8380f7f719 100644 --- a/.opencode/opencode.jsonc +++ b/.opencode/opencode.jsonc @@ -5,6 +5,11 @@ "options": {}, }, }, + "permission": { + "edit": { + "packages/opencode/migration/*": "deny", + }, + }, "mcp": {}, "tools": { "github-triage": false, From b749fa90f23d187d5428de1a2d321cc6497b6667 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:43:56 -0500 Subject: [PATCH 021/200] fix(app): scroll jitter/loop --- packages/app/src/pages/session.tsx | 78 ++--- .../src/pages/session/message-timeline.tsx | 11 +- .../app/src/pages/session/scroll-spy.test.ts | 127 -------- packages/app/src/pages/session/scroll-spy.ts | 275 ------------------ .../pages/session/use-session-hash-scroll.ts | 86 +++--- .../ui/src/components/scroll-view.test.ts | 19 ++ packages/ui/src/components/scroll-view.tsx | 36 ++- 7 files changed, 144 insertions(+), 488 deletions(-) delete mode 100644 packages/app/src/pages/session/scroll-spy.test.ts delete mode 100644 packages/app/src/pages/session/scroll-spy.ts create mode 100644 packages/ui/src/components/scroll-view.test.ts diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 35e89eabe4..c1552ad024 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -37,7 +37,6 @@ import { createOpenReviewFile, createSizing } from "@/pages/session/helpers" import { MessageTimeline } from "@/pages/session/message-timeline" import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab" import { resetSessionModel, syncSessionModel } from "@/pages/session/session-model-helpers" -import { createScrollSpy } from "@/pages/session/scroll-spy" import { SessionMobileTabs } from "@/pages/session/session-mobile-tabs" import { SessionSidePanel } from "@/pages/session/session-side-panel" import { TerminalPanel } from "@/pages/session/terminal-panel" @@ -486,20 +485,49 @@ export default function Page() { return "main" }) - const activeMessage = createMemo(() => { - if (!store.messageId) return lastUserMessage() - const found = visibleUserMessages()?.find((m) => m.id === store.messageId) - return found ?? lastUserMessage() - }) const setActiveMessage = (message: UserMessage | undefined) => { + messageMark = scrollMark setStore("messageId", message?.id) } + const anchor = (id: string) => `message-${id}` + + const cursor = () => { + const root = scroller + if (!root) return store.messageId + + const box = root.getBoundingClientRect() + const line = box.top + 100 + const list = [...root.querySelectorAll<HTMLElement>("[data-message-id]")] + .map((el) => { + const id = el.dataset.messageId + if (!id) return + + const rect = el.getBoundingClientRect() + return { id, top: rect.top, bottom: rect.bottom } + }) + .filter((item): item is { id: string; top: number; bottom: number } => !!item) + + const shown = list.filter((item) => item.bottom > box.top && item.top < box.bottom) + const hit = shown.find((item) => item.top <= line && item.bottom >= line) + if (hit) return hit.id + + const near = [...shown].sort((a, b) => { + const da = Math.abs(a.top - line) + const db = Math.abs(b.top - line) + if (da !== db) return da - db + return a.top - b.top + })[0] + if (near) return near.id + + return list.filter((item) => item.top <= line).at(-1)?.id ?? list[0]?.id ?? store.messageId + } + function navigateMessageByOffset(offset: number) { const msgs = visibleUserMessages() if (msgs.length === 0) return - const current = store.messageId + const current = store.messageId && messageMark === scrollMark ? store.messageId : cursor() const base = current ? msgs.findIndex((m) => m.id === current) : msgs.length const currentIndex = base === -1 ? msgs.length : base const targetIndex = currentIndex + offset @@ -572,6 +600,8 @@ export default function Page() { let dockHeight = 0 let scroller: HTMLDivElement | undefined let content: HTMLDivElement | undefined + let scrollMark = 0 + let messageMark = 0 const scrollGestureWindowMs = 250 @@ -616,6 +646,7 @@ export default function Page() { () => { setStore("messageId", undefined) setStore("changes", "session") + setUi("pendingMessage", undefined) }, { defer: true }, ), @@ -1110,12 +1141,6 @@ export default function Page() { let scrollStateFrame: number | undefined let scrollStateTarget: HTMLDivElement | undefined - const scrollSpy = createScrollSpy({ - onActive: (id) => { - if (id === store.messageId) return - setStore("messageId", id) - }, - }) const updateScrollState = (el: HTMLDivElement) => { const max = el.scrollHeight - el.clientHeight @@ -1163,31 +1188,21 @@ export default function Page() { ), ) - createEffect( - on( - sessionKey, - () => { - scrollSpy.clear() - }, - { defer: true }, - ), - ) - - const anchor = (id: string) => `message-${id}` - const setScrollRef = (el: HTMLDivElement | undefined) => { scroller = el autoScroll.scrollRef(el) - scrollSpy.setContainer(el) if (el) scheduleScrollState(el) } + const markUserScroll = () => { + scrollMark += 1 + } + createResizeObserver( () => content, () => { const el = scroller if (el) scheduleScrollState(el) - scrollSpy.markDirty() }, ) @@ -1220,7 +1235,6 @@ export default function Page() { if (stick) autoScroll.forceScrollToBottom() if (el) scheduleScrollState(el) - scrollSpy.markDirty() }, ) @@ -1248,7 +1262,6 @@ export default function Page() { onCleanup(() => { document.removeEventListener("keydown", handleKeyDown) - scrollSpy.destroy() if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame) if (scrollStateFrame !== undefined) cancelAnimationFrame(scrollStateFrame) }) @@ -1280,7 +1293,7 @@ export default function Page() { <div class="flex-1 min-h-0 overflow-hidden"> <Switch> <Match when={params.id}> - <Show when={activeMessage()}> + <Show when={lastUserMessage()}> <MessageTimeline mobileChanges={mobileChanges()} mobileFallback={reviewContent({ @@ -1300,8 +1313,7 @@ export default function Page() { onAutoScrollHandleScroll={autoScroll.handleScroll} onMarkScrollGesture={markScrollGesture} hasScrollGesture={hasScrollGesture} - isDesktop={isDesktop()} - onScrollSpyScroll={scrollSpy.onScroll} + onUserScroll={markUserScroll} onTurnBackfillScroll={historyWindow.onScrollerScroll} onAutoScrollInteraction={autoScroll.handleInteraction} centered={centered()} @@ -1320,8 +1332,6 @@ export default function Page() { }} renderedUserMessages={historyWindow.renderedUserMessages()} anchor={anchor} - onRegisterMessage={scrollSpy.register} - onUnregisterMessage={scrollSpy.unregister} /> </Show> </Match> diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 4060e5e5c2..6463e7cbbe 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -193,8 +193,7 @@ export function MessageTimeline(props: { onAutoScrollHandleScroll: () => void onMarkScrollGesture: (target?: EventTarget | null) => void hasScrollGesture: () => boolean - isDesktop: boolean - onScrollSpyScroll: () => void + onUserScroll: () => void onTurnBackfillScroll: () => void onAutoScrollInteraction: (event: MouseEvent) => void centered: boolean @@ -205,8 +204,6 @@ export function MessageTimeline(props: { onLoadEarlier: () => void renderedUserMessages: UserMessage[] anchor: (id: string) => string - onRegisterMessage: (el: HTMLDivElement, id: string) => void - onUnregisterMessage: (id: string) => void }) { let touchGesture: number | undefined @@ -574,9 +571,9 @@ export function MessageTimeline(props: { props.onScheduleScrollState(e.currentTarget) props.onTurnBackfillScroll() if (!props.hasScrollGesture()) return + props.onUserScroll() props.onAutoScrollHandleScroll() props.onMarkScrollGesture(e.currentTarget) - if (props.isDesktop) props.onScrollSpyScroll() }} onClick={props.onAutoScrollInteraction} class="relative min-w-0 w-full h-full" @@ -763,10 +760,6 @@ export function MessageTimeline(props: { <div id={props.anchor(messageID)} data-message-id={messageID} - ref={(el) => { - props.onRegisterMessage(el, messageID) - onCleanup(() => props.onUnregisterMessage(messageID)) - }} classList={{ "min-w-0 w-full max-w-full": true, "md:max-w-200 2xl:max-w-[1000px]": props.centered, diff --git a/packages/app/src/pages/session/scroll-spy.test.ts b/packages/app/src/pages/session/scroll-spy.test.ts deleted file mode 100644 index f3e6775cb4..0000000000 --- a/packages/app/src/pages/session/scroll-spy.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { createScrollSpy, pickOffsetId, pickVisibleId } from "./scroll-spy" - -const rect = (top: number, height = 80): DOMRect => - ({ - x: 0, - y: top, - top, - left: 0, - right: 800, - bottom: top + height, - width: 800, - height, - toJSON: () => ({}), - }) as DOMRect - -const setRect = (el: Element, top: number, height = 80) => { - Object.defineProperty(el, "getBoundingClientRect", { - configurable: true, - value: () => rect(top, height), - }) -} - -describe("pickVisibleId", () => { - test("prefers higher intersection ratio", () => { - const id = pickVisibleId( - [ - { id: "a", ratio: 0.2, top: 100 }, - { id: "b", ratio: 0.8, top: 300 }, - ], - 120, - ) - - expect(id).toBe("b") - }) - - test("breaks ratio ties by nearest line", () => { - const id = pickVisibleId( - [ - { id: "a", ratio: 0.5, top: 90 }, - { id: "b", ratio: 0.5, top: 140 }, - ], - 130, - ) - - expect(id).toBe("b") - }) -}) - -describe("pickOffsetId", () => { - test("uses binary search cutoff", () => { - const id = pickOffsetId( - [ - { id: "a", top: 0 }, - { id: "b", top: 200 }, - { id: "c", top: 400 }, - ], - 350, - ) - - expect(id).toBe("b") - }) -}) - -describe("createScrollSpy fallback", () => { - test("tracks active id from offsets and dirty refresh", () => { - const active: string[] = [] - const root = document.createElement("div") as HTMLDivElement - const one = document.createElement("div") - const two = document.createElement("div") - const three = document.createElement("div") - - root.append(one, two, three) - document.body.append(root) - - Object.defineProperty(root, "scrollTop", { configurable: true, writable: true, value: 250 }) - setRect(root, 0, 800) - setRect(one, -250) - setRect(two, -50) - setRect(three, 150) - - const queue: FrameRequestCallback[] = [] - const flush = () => { - const run = [...queue] - queue.length = 0 - for (const cb of run) cb(0) - } - - const spy = createScrollSpy({ - onActive: (id) => active.push(id), - raf: (cb) => (queue.push(cb), queue.length), - caf: () => {}, - IntersectionObserver: undefined, - ResizeObserver: undefined, - MutationObserver: undefined, - }) - - spy.setContainer(root) - spy.register(one, "a") - spy.register(two, "b") - spy.register(three, "c") - spy.onScroll() - flush() - - expect(spy.getActiveId()).toBe("b") - expect(active.at(-1)).toBe("b") - - root.scrollTop = 450 - setRect(one, -450) - setRect(two, -250) - setRect(three, -50) - spy.onScroll() - flush() - expect(spy.getActiveId()).toBe("c") - - root.scrollTop = 250 - setRect(one, -250) - setRect(two, 250) - setRect(three, 150) - spy.markDirty() - spy.onScroll() - flush() - expect(spy.getActiveId()).toBe("a") - - spy.destroy() - }) -}) diff --git a/packages/app/src/pages/session/scroll-spy.ts b/packages/app/src/pages/session/scroll-spy.ts deleted file mode 100644 index 6ef4c844c4..0000000000 --- a/packages/app/src/pages/session/scroll-spy.ts +++ /dev/null @@ -1,275 +0,0 @@ -type Visible = { - id: string - ratio: number - top: number -} - -type Offset = { - id: string - top: number -} - -type Input = { - onActive: (id: string) => void - raf?: (cb: FrameRequestCallback) => number - caf?: (id: number) => void - IntersectionObserver?: typeof globalThis.IntersectionObserver - ResizeObserver?: typeof globalThis.ResizeObserver - MutationObserver?: typeof globalThis.MutationObserver -} - -export const pickVisibleId = (list: Visible[], line: number) => { - if (list.length === 0) return - - const sorted = [...list].sort((a, b) => { - if (b.ratio !== a.ratio) return b.ratio - a.ratio - - const da = Math.abs(a.top - line) - const db = Math.abs(b.top - line) - if (da !== db) return da - db - - return a.top - b.top - }) - - return sorted[0]?.id -} - -export const pickOffsetId = (list: Offset[], cutoff: number) => { - if (list.length === 0) return - - let lo = 0 - let hi = list.length - 1 - let out = 0 - - while (lo <= hi) { - const mid = (lo + hi) >> 1 - const top = list[mid]?.top - if (top === undefined) break - - if (top <= cutoff) { - out = mid - lo = mid + 1 - continue - } - - hi = mid - 1 - } - - return list[out]?.id -} - -export const createScrollSpy = (input: Input) => { - const raf = input.raf ?? requestAnimationFrame - const caf = input.caf ?? cancelAnimationFrame - const CtorIO = input.IntersectionObserver ?? globalThis.IntersectionObserver - const CtorRO = input.ResizeObserver ?? globalThis.ResizeObserver - const CtorMO = input.MutationObserver ?? globalThis.MutationObserver - - let root: HTMLDivElement | undefined - let io: IntersectionObserver | undefined - let ro: ResizeObserver | undefined - let mo: MutationObserver | undefined - let frame: number | undefined - let active: string | undefined - let dirty = true - - const node = new Map<string, HTMLElement>() - const id = new WeakMap<HTMLElement, string>() - const visible = new Map<string, { ratio: number; top: number }>() - let offset: Offset[] = [] - - const schedule = () => { - if (frame !== undefined) return - frame = raf(() => { - frame = undefined - update() - }) - } - - const refreshOffset = () => { - const el = root - if (!el) { - offset = [] - dirty = false - return - } - - const base = el.getBoundingClientRect().top - offset = [...node].map(([next, item]) => ({ - id: next, - top: item.getBoundingClientRect().top - base + el.scrollTop, - })) - offset.sort((a, b) => a.top - b.top) - dirty = false - } - - const update = () => { - const el = root - if (!el) return - - const line = el.getBoundingClientRect().top + 100 - const next = - pickVisibleId( - [...visible].map(([k, v]) => ({ - id: k, - ratio: v.ratio, - top: v.top, - })), - line, - ) ?? - (() => { - if (dirty) refreshOffset() - return pickOffsetId(offset, el.scrollTop + 100) - })() - - if (!next || next === active) return - active = next - input.onActive(next) - } - - const observe = () => { - const el = root - if (!el) return - - io?.disconnect() - io = undefined - if (CtorIO) { - try { - io = new CtorIO( - (entries) => { - for (const entry of entries) { - const item = entry.target - if (!(item instanceof HTMLElement)) continue - const key = id.get(item) - if (!key) continue - - if (!entry.isIntersecting || entry.intersectionRatio <= 0) { - visible.delete(key) - continue - } - - visible.set(key, { - ratio: entry.intersectionRatio, - top: entry.boundingClientRect.top, - }) - } - - schedule() - }, - { - root: el, - threshold: [0, 0.25, 0.5, 0.75, 1], - }, - ) - } catch { - io = undefined - } - } - - if (io) { - for (const item of node.values()) io.observe(item) - } - - ro?.disconnect() - ro = undefined - if (CtorRO) { - ro = new CtorRO(() => { - dirty = true - schedule() - }) - ro.observe(el) - for (const item of node.values()) ro.observe(item) - } - - mo?.disconnect() - mo = undefined - if (CtorMO) { - mo = new CtorMO(() => { - dirty = true - schedule() - }) - mo.observe(el, { subtree: true, childList: true, characterData: true }) - } - - dirty = true - schedule() - } - - const setContainer = (el?: HTMLDivElement) => { - if (root === el) return - - root = el - visible.clear() - active = undefined - observe() - } - - const register = (el: HTMLElement, key: string) => { - const prev = node.get(key) - if (prev && prev !== el) { - io?.unobserve(prev) - ro?.unobserve(prev) - } - - node.set(key, el) - id.set(el, key) - if (io) io.observe(el) - if (ro) ro.observe(el) - dirty = true - schedule() - } - - const unregister = (key: string) => { - const item = node.get(key) - if (!item) return - - io?.unobserve(item) - ro?.unobserve(item) - node.delete(key) - visible.delete(key) - dirty = true - schedule() - } - - const markDirty = () => { - dirty = true - schedule() - } - - const clear = () => { - for (const item of node.values()) { - io?.unobserve(item) - ro?.unobserve(item) - } - - node.clear() - visible.clear() - offset = [] - active = undefined - dirty = true - } - - const destroy = () => { - if (frame !== undefined) caf(frame) - frame = undefined - clear() - io?.disconnect() - ro?.disconnect() - mo?.disconnect() - io = undefined - ro = undefined - mo = undefined - root = undefined - } - - return { - setContainer, - register, - unregister, - onScroll: schedule, - markDirty, - clear, - destroy, - getActiveId: () => active, - } -} 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 20e88a3ea3..1ea6a302b9 100644 --- a/packages/app/src/pages/session/use-session-hash-scroll.ts +++ b/packages/app/src/pages/session/use-session-hash-scroll.ts @@ -1,6 +1,6 @@ import type { UserMessage } from "@opencode-ai/sdk/v2" import { useLocation, useNavigate } from "@solidjs/router" -import { createEffect, createMemo, onMount } from "solid-js" +import { createEffect, createMemo, onCleanup, onMount } from "solid-js" import { messageIdFromHash } from "./message-id-from-hash" export { messageIdFromHash } from "./message-id-from-hash" @@ -26,17 +26,38 @@ export const useSessionHashScroll = (input: { const messageById = createMemo(() => new Map(visibleUserMessages().map((m) => [m.id, m]))) const messageIndex = createMemo(() => new Map(visibleUserMessages().map((m, i) => [m.id, i]))) let pendingKey = "" + let clearing = false const location = useLocation() const navigate = useNavigate() + const frames = new Set<number>() + const queue = (fn: () => void) => { + const id = requestAnimationFrame(() => { + frames.delete(id) + fn() + }) + frames.add(id) + } + const cancel = () => { + for (const id of frames) cancelAnimationFrame(id) + frames.clear() + } + const clearMessageHash = () => { + cancel() + input.consumePendingMessage(input.sessionKey()) + if (input.pendingMessage()) input.setPendingMessage(undefined) if (!location.hash) return + clearing = true navigate(location.pathname + location.search, { replace: true }) } const updateHash = (id: string) => { - navigate(location.pathname + location.search + `#${input.anchor(id)}`, { + const hash = `#${input.anchor(id)}` + if (location.hash === hash) return + clearing = false + navigate(location.pathname + location.search + hash, { replace: true, }) } @@ -54,51 +75,37 @@ export const useSessionHashScroll = (input: { return true } + const seek = (id: string, behavior: ScrollBehavior, left = 4): boolean => { + const el = document.getElementById(input.anchor(id)) + if (el) return scrollToElement(el, behavior) + if (left <= 0) return false + queue(() => { + seek(id, behavior, left - 1) + }) + return false + } + const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => { - console.log({ message, behavior }) + cancel() if (input.currentMessageId() !== message.id) input.setActiveMessage(message) const index = messageIndex().get(message.id) ?? -1 if (index !== -1 && index < input.turnStart()) { input.setTurnStart(index) - requestAnimationFrame(() => { - const el = document.getElementById(input.anchor(message.id)) - if (!el) { - requestAnimationFrame(() => { - const next = document.getElementById(input.anchor(message.id)) - if (!next) return - scrollToElement(next, behavior) - }) - return - } - scrollToElement(el, behavior) + queue(() => { + seek(message.id, behavior) }) updateHash(message.id) return } - const el = document.getElementById(input.anchor(message.id)) - if (!el) { - updateHash(message.id) - requestAnimationFrame(() => { - const next = document.getElementById(input.anchor(message.id)) - if (!next) return - if (!scrollToElement(next, behavior)) return - }) - return - } - if (scrollToElement(el, behavior)) { + if (seek(message.id, behavior)) { updateHash(message.id) return } - requestAnimationFrame(() => { - const next = document.getElementById(input.anchor(message.id)) - if (!next) return - if (!scrollToElement(next, behavior)) return - }) updateHash(message.id) } @@ -135,9 +142,11 @@ export const useSessionHashScroll = (input: { } createEffect(() => { - location.hash + const hash = location.hash + if (!hash) clearing = false if (!input.sessionID() || !input.messagesReady()) return - requestAnimationFrame(() => applyHash("auto")) + cancel() + queue(() => applyHash("auto")) }) createEffect(() => { @@ -159,16 +168,19 @@ export const useSessionHashScroll = (input: { } } - if (!targetId) targetId = messageIdFromHash(location.hash) + if (!targetId && !clearing) targetId = messageIdFromHash(location.hash) if (!targetId) return - if (input.currentMessageId() === targetId) return + const pending = input.pendingMessage() === targetId const msg = messageById().get(targetId) if (!msg) return - if (input.pendingMessage() === targetId) input.setPendingMessage(undefined) + if (pending) input.setPendingMessage(undefined) + if (input.currentMessageId() === targetId && !pending) return + input.autoScroll.pause() - requestAnimationFrame(() => scrollToMessage(msg, "auto")) + cancel() + queue(() => scrollToMessage(msg, "auto")) }) onMount(() => { @@ -177,6 +189,8 @@ export const useSessionHashScroll = (input: { } }) + onCleanup(cancel) + return { clearMessageHash, scrollToMessage, diff --git a/packages/ui/src/components/scroll-view.test.ts b/packages/ui/src/components/scroll-view.test.ts new file mode 100644 index 0000000000..d28b51fea8 --- /dev/null +++ b/packages/ui/src/components/scroll-view.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "bun:test" +import { scrollKey } from "./scroll-view" + +describe("scrollKey", () => { + test("maps plain navigation keys", () => { + expect(scrollKey({ key: "PageDown", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false })).toBe( + "page-down", + ) + expect(scrollKey({ key: "ArrowUp", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false })).toBe("up") + }) + + test("ignores modified keybinds", () => { + expect( + scrollKey({ key: "ArrowDown", altKey: false, ctrlKey: false, metaKey: true, shiftKey: false }), + ).toBeUndefined() + expect(scrollKey({ key: "PageUp", altKey: false, ctrlKey: true, metaKey: false, shiftKey: false })).toBeUndefined() + expect(scrollKey({ key: "End", altKey: false, ctrlKey: false, metaKey: false, shiftKey: true })).toBeUndefined() + }) +}) diff --git a/packages/ui/src/components/scroll-view.tsx b/packages/ui/src/components/scroll-view.tsx index 52ed39a465..c3d878af63 100644 --- a/packages/ui/src/components/scroll-view.tsx +++ b/packages/ui/src/components/scroll-view.tsx @@ -6,6 +6,25 @@ export interface ScrollViewProps extends ComponentProps<"div"> { orientation?: "vertical" | "horizontal" // currently only vertical is fully implemented for thumb } +export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => { + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return + + switch (event.key) { + case "PageDown": + return "page-down" + case "PageUp": + return "page-up" + case "Home": + return "home" + case "End": + return "end" + case "ArrowUp": + return "up" + case "ArrowDown": + return "down" + } +} + export function ScrollView(props: ScrollViewProps) { const i18n = useI18n() const merged = mergeProps({ orientation: "vertical" }, props) @@ -133,31 +152,34 @@ export function ScrollView(props: ScrollViewProps) { return } + const next = scrollKey(e) + if (!next) return + const scrollAmount = viewportRef.clientHeight * 0.8 const lineAmount = 40 - switch (e.key) { - case "PageDown": + switch (next) { + case "page-down": e.preventDefault() viewportRef.scrollBy({ top: scrollAmount, behavior: "smooth" }) break - case "PageUp": + case "page-up": e.preventDefault() viewportRef.scrollBy({ top: -scrollAmount, behavior: "smooth" }) break - case "Home": + case "home": e.preventDefault() viewportRef.scrollTo({ top: 0, behavior: "smooth" }) break - case "End": + case "end": e.preventDefault() viewportRef.scrollTo({ top: viewportRef.scrollHeight, behavior: "smooth" }) break - case "ArrowUp": + case "up": e.preventDefault() viewportRef.scrollBy({ top: -lineAmount, behavior: "smooth" }) break - case "ArrowDown": + case "down": e.preventDefault() viewportRef.scrollBy({ top: lineAmount, behavior: "smooth" }) break From c6262f9d4002d86a1f1795c306aa329d45361d12 Mon Sep 17 00:00:00 2001 From: opencode <opencode@sst.dev> Date: Mon, 9 Mar 2026 16:09:34 +0000 Subject: [PATCH 022/200] release: v1.2.24 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index ca466664fe..bb13d0ea40 100644 --- a/bun.lock +++ b/bun.lock @@ -26,7 +26,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -76,7 +76,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -110,7 +110,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -137,7 +137,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -161,7 +161,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -185,7 +185,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -218,7 +218,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -248,7 +248,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -277,7 +277,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -293,7 +293,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.2.23", + "version": "1.2.24", "bin": { "opencode": "./bin/opencode", }, @@ -409,7 +409,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -429,7 +429,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.2.23", + "version": "1.2.24", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -440,7 +440,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -475,7 +475,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -521,7 +521,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "zod": "catalog:", }, @@ -532,7 +532,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 62e4b0b730..10ef17d1bf 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.2.23", + "version": "1.2.24", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index cb068ce5ad..2cbc8f7575 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.2.23", + "version": "1.2.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 721f0fbf9a..209a0e2df3 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.2.23", + "version": "1.2.24", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 60992deb2e..377ab97cf6 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.2.23", + "version": "1.2.24", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 6b28502c0e..9a383f19fb 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.2.23", + "version": "1.2.24", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 3a6b8be814..45fa7355f8 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.2.23", + "version": "1.2.24", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 592f6f6a46..b028c840ba 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.2.23", + "version": "1.2.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 8fd76f59cd..1b14e3f142 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.2.23", + "version": "1.2.24", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 34d48fd5cf..44cdeb9d94 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.2.23" +version = "1.2.24" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.24/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.24/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.24/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.24/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.23/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.24/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 1e703fd760..7b64af1f99 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.2.23", + "version": "1.2.24", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index cf727cea97..c560315d33 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.2.23", + "version": "1.2.24", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 3162a7d344..3f1f6af95f 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.2.23", + "version": "1.2.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 9615c7a155..7281174c0f 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.2.23", + "version": "1.2.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 9db64eed62..4ed7386e8a 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.2.23", + "version": "1.2.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index ae24225105..06c699f24e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.2.23", + "version": "1.2.24", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 8d037e0ba9..1b36f37484 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.2.23", + "version": "1.2.24", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 41318febb7..378493853b 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.2.23", + "version": "1.2.24", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 4c0655a882..6eedb0d751 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.2.23", + "version": "1.2.24", "publisher": "sst-dev", "repository": { "type": "git", From 8b9710e56c041cae4bdc0b865dc4d13bc04edebf Mon Sep 17 00:00:00 2001 From: Stephen Collings <stevoland@gmail.com> Date: Mon, 9 Mar 2026 16:04:08 +0000 Subject: [PATCH 023/200] fix: Multiple jdtls LSPs eating memory in java monorepos (#12123) --- packages/opencode/src/lsp/server.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index e09fbc97fe..405c672853 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1130,7 +1130,30 @@ export namespace LSPServer { export const JDTLS: Info = { id: "jdtls", - root: NearestRoot(["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"]), + root: async (file) => { + // Without exclusions, NearestRoot defaults to instance directory so we can't + // distinguish between a) no project found and b) project found at instance dir. + // So we can't choose the root from (potential) monorepo markers first. + // Look for potential subproject markers first while excluding potential monorepo markers. + const settingsMarkers = ["settings.gradle", "settings.gradle.kts"] + const gradleMarkers = ["gradlew", "gradlew.bat"] + const exclusionsForMonorepos = gradleMarkers.concat(settingsMarkers) + + const [projectRoot, wrapperRoot, settingsRoot] = await Promise.all([ + NearestRoot( + ["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"], + exclusionsForMonorepos, + )(file), + NearestRoot(gradleMarkers, settingsMarkers)(file), + NearestRoot(settingsMarkers)(file), + ]) + + // If projectRoot is undefined we know we are in a monorepo or no project at all. + // So can safely fall through to the other roots + if (projectRoot) return projectRoot + if (wrapperRoot) return wrapperRoot + if (settingsRoot) return settingsRoot + }, extensions: [".java"], async spawn(root) { const java = which("java") From f20ee2fad2b7e67568e08c51ab476aee3a31d089 Mon Sep 17 00:00:00 2001 From: James Long <longster@gmail.com> Date: Mon, 9 Mar 2026 12:13:32 -0400 Subject: [PATCH 024/200] fix(tui): handle error when creating a session (#16767) --- .../cli/cmd/tui/component/prompt/index.tsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index d63c248fb8..77577b2a0e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -539,12 +539,25 @@ export function Prompt(props: PromptProps) { promptModelWarning() return } - const sessionID = props.sessionID - ? props.sessionID - : await (async () => { - const sessionID = await sdk.client.session.create({}).then((x) => x.data!.id) - return sessionID - })() + + let sessionID = props.sessionID + if (sessionID == null) { + const res = await sdk.client.session.create({}) + + if (res.error) { + console.log("Creating a session failed:", res.error) + + toast.show({ + message: "Creating a session failed. Open console for more details.", + variant: "error", + }) + + return + } + + sessionID = res.data.id + } + const messageID = Identifier.ascending("message") let inputText = store.prompt.input From 831eb6881b98e74a470b04ae292b95040d752ef3 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Mon, 9 Mar 2026 14:52:25 -0400 Subject: [PATCH 025/200] refactor: change pathToFileURL imports from bun to url module --- packages/opencode/src/acp/agent.ts | 2 +- packages/opencode/src/cli/cmd/run.ts | 2 +- packages/opencode/src/session/prompt.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index d518dd12a1..2552682dbe 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -29,7 +29,7 @@ import { } from "@agentclientprotocol/sdk" import { Log } from "../util/log" -import { pathToFileURL } from "bun" +import { pathToFileURL } from "url" import { Filesystem } from "../util/filesystem" import { Hash } from "../util/hash" import { ACPSessionManager } from "./session" diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 5c6dd11f99..ab30e27148 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -1,6 +1,6 @@ import type { Argv } from "yargs" import path from "path" -import { pathToFileURL } from "bun" +import { pathToFileURL } from "url" import { UI } from "../ui" import { cmd } from "./cmd" import { Flag } from "../../flag/flag" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 4f77920cc9..7698b78bab 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -31,7 +31,8 @@ import { Flag } from "../flag/flag" import { ulid } from "ulid" import { spawn } from "child_process" import { Command } from "../command" -import { $, fileURLToPath, pathToFileURL } from "bun" +import { $ } from "bun" +import { pathToFileURL, fileURLToPath } from "url" import { ConfigMarkdown } from "../config/markdown" import { SessionSummary } from "./summary" import { NamedError } from "@opencode-ai/util/error" From 2f2856e20ad3433e6d82ff8d2e51f4ff14f9f098 Mon Sep 17 00:00:00 2001 From: Dax <mail@thdxr.com> Date: Mon, 9 Mar 2026 15:19:50 -0400 Subject: [PATCH 026/200] refactor(opencode): replace Bun shell in core flows (#16286) --- packages/opencode/src/cli/cmd/github.ts | 98 +++--- packages/opencode/src/cli/cmd/pr.ts | 47 ++- .../src/cli/cmd/tui/util/clipboard.ts | 47 ++- packages/opencode/src/cli/cmd/uninstall.ts | 19 +- packages/opencode/src/file/index.ts | 52 ++-- packages/opencode/src/file/ripgrep.ts | 14 +- packages/opencode/src/file/watcher.ts | 13 +- packages/opencode/src/installation/index.ts | 118 ++++--- packages/opencode/src/lsp/server.ts | 113 ++++--- packages/opencode/src/project/vcs.ts | 16 +- packages/opencode/src/snapshot/index.ts | 293 ++++++++++++------ packages/opencode/src/storage/storage.ts | 21 +- packages/opencode/src/tool/bash.ts | 9 +- packages/opencode/src/util/archive.ts | 9 +- packages/opencode/src/util/process.ts | 36 ++- packages/opencode/src/worktree/index.ts | 71 +++-- packages/opencode/test/util/module.test.ts | 59 ++++ packages/util/src/module.ts | 10 + 18 files changed, 681 insertions(+), 364 deletions(-) create mode 100644 packages/opencode/test/util/module.test.ts create mode 100644 packages/util/src/module.ts diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 2491abc567..a58151308c 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -27,8 +27,9 @@ import { Provider } from "../../provider/provider" import { Bus } from "../../bus" import { MessageV2 } from "../../session/message-v2" import { SessionPrompt } from "@/session/prompt" -import { $ } from "bun" import { setTimeout as sleep } from "node:timers/promises" +import { Process } from "@/util/process" +import { git } from "@/util/git" type GitHubAuthor = { login: string @@ -255,7 +256,7 @@ export const GithubInstallCommand = cmd({ } // Get repo info - const info = (await $`git remote get-url origin`.quiet().nothrow().text()).trim() + const info = (await git(["remote", "get-url", "origin"], { cwd: Instance.worktree })).text().trim() const parsed = parseGitHubRemote(info) if (!parsed) { prompts.log.error(`Could not find git repository. Please run this command from a git repository.`) @@ -493,6 +494,26 @@ export const GithubRunCommand = cmd({ ? "pr_review" : "issue" : undefined + const gitText = async (args: string[]) => { + const result = await git(args, { cwd: Instance.worktree }) + if (result.exitCode !== 0) { + throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr) + } + return result.text().trim() + } + const gitRun = async (args: string[]) => { + const result = await git(args, { cwd: Instance.worktree }) + if (result.exitCode !== 0) { + throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr) + } + return result + } + const gitStatus = (args: string[]) => git(args, { cwd: Instance.worktree }) + const commitChanges = async (summary: string, actor?: string) => { + const args = ["commit", "-m", summary] + if (actor) args.push("-m", `Co-authored-by: ${actor} <${actor}@users.noreply.github.com>`) + await gitRun(args) + } try { if (useGithubToken) { @@ -553,7 +574,7 @@ export const GithubRunCommand = cmd({ } const branchPrefix = isWorkflowDispatchEvent ? "dispatch" : "schedule" const branch = await checkoutNewBranch(branchPrefix) - const head = (await $`git rev-parse HEAD`).stdout.toString().trim() + const head = await gitText(["rev-parse", "HEAD"]) const response = await chat(userPrompt, promptFiles) const { dirty, uncommittedChanges, switched } = await branchIsDirty(head, branch) if (switched) { @@ -587,7 +608,7 @@ export const GithubRunCommand = cmd({ // Local PR if (prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner) { await checkoutLocalBranch(prData) - const head = (await $`git rev-parse HEAD`).stdout.toString().trim() + const head = await gitText(["rev-parse", "HEAD"]) const dataPrompt = buildPromptDataForPR(prData) const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles) const { dirty, uncommittedChanges, switched } = await branchIsDirty(head, prData.headRefName) @@ -605,7 +626,7 @@ export const GithubRunCommand = cmd({ // Fork PR else { const forkBranch = await checkoutForkBranch(prData) - const head = (await $`git rev-parse HEAD`).stdout.toString().trim() + const head = await gitText(["rev-parse", "HEAD"]) const dataPrompt = buildPromptDataForPR(prData) const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles) const { dirty, uncommittedChanges, switched } = await branchIsDirty(head, forkBranch) @@ -624,7 +645,7 @@ export const GithubRunCommand = cmd({ // Issue else { const branch = await checkoutNewBranch("issue") - const head = (await $`git rev-parse HEAD`).stdout.toString().trim() + const head = await gitText(["rev-parse", "HEAD"]) const issueData = await fetchIssue() const dataPrompt = buildPromptDataForIssue(issueData) const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles) @@ -658,7 +679,7 @@ export const GithubRunCommand = cmd({ exitCode = 1 console.error(e instanceof Error ? e.message : String(e)) let msg = e - if (e instanceof $.ShellError) { + if (e instanceof Process.RunFailedError) { msg = e.stderr.toString() } else if (e instanceof Error) { msg = e.message @@ -1049,29 +1070,29 @@ export const GithubRunCommand = cmd({ const config = "http.https://github.com/.extraheader" // actions/checkout@v6 no longer stores credentials in .git/config, // so this may not exist - use nothrow() to handle gracefully - const ret = await $`git config --local --get ${config}`.nothrow() + const ret = await gitStatus(["config", "--local", "--get", config]) if (ret.exitCode === 0) { gitConfig = ret.stdout.toString().trim() - await $`git config --local --unset-all ${config}` + await gitRun(["config", "--local", "--unset-all", config]) } const newCredentials = Buffer.from(`x-access-token:${appToken}`, "utf8").toString("base64") - await $`git config --local ${config} "AUTHORIZATION: basic ${newCredentials}"` - await $`git config --global user.name "${AGENT_USERNAME}"` - await $`git config --global user.email "${AGENT_USERNAME}@users.noreply.github.com"` + await gitRun(["config", "--local", config, `AUTHORIZATION: basic ${newCredentials}`]) + await gitRun(["config", "--global", "user.name", AGENT_USERNAME]) + await gitRun(["config", "--global", "user.email", `${AGENT_USERNAME}@users.noreply.github.com`]) } async function restoreGitConfig() { if (gitConfig === undefined) return const config = "http.https://github.com/.extraheader" - await $`git config --local ${config} "${gitConfig}"` + await gitRun(["config", "--local", config, gitConfig]) } async function checkoutNewBranch(type: "issue" | "schedule" | "dispatch") { console.log("Checking out new branch...") const branch = generateBranchName(type) - await $`git checkout -b ${branch}` + await gitRun(["checkout", "-b", branch]) return branch } @@ -1081,8 +1102,8 @@ export const GithubRunCommand = cmd({ const branch = pr.headRefName const depth = Math.max(pr.commits.totalCount, 20) - await $`git fetch origin --depth=${depth} ${branch}` - await $`git checkout ${branch}` + await gitRun(["fetch", "origin", `--depth=${depth}`, branch]) + await gitRun(["checkout", branch]) } async function checkoutForkBranch(pr: GitHubPullRequest) { @@ -1092,9 +1113,9 @@ export const GithubRunCommand = cmd({ const localBranch = generateBranchName("pr") const depth = Math.max(pr.commits.totalCount, 20) - await $`git remote add fork https://github.com/${pr.headRepository.nameWithOwner}.git` - await $`git fetch fork --depth=${depth} ${remoteBranch}` - await $`git checkout -b ${localBranch} fork/${remoteBranch}` + await gitRun(["remote", "add", "fork", `https://github.com/${pr.headRepository.nameWithOwner}.git`]) + await gitRun(["fetch", "fork", `--depth=${depth}`, remoteBranch]) + await gitRun(["checkout", "-b", localBranch, `fork/${remoteBranch}`]) return localBranch } @@ -1115,28 +1136,23 @@ export const GithubRunCommand = cmd({ async function pushToNewBranch(summary: string, branch: string, commit: boolean, isSchedule: boolean) { console.log("Pushing to new branch...") if (commit) { - await $`git add .` + await gitRun(["add", "."]) if (isSchedule) { - // No co-author for scheduled events - the schedule is operating as the repo - await $`git commit -m "${summary}"` + await commitChanges(summary) } else { - await $`git commit -m "${summary} - -Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"` + await commitChanges(summary, actor) } } - await $`git push -u origin ${branch}` + await gitRun(["push", "-u", "origin", branch]) } async function pushToLocalBranch(summary: string, commit: boolean) { console.log("Pushing to local branch...") if (commit) { - await $`git add .` - await $`git commit -m "${summary} - -Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"` + await gitRun(["add", "."]) + await commitChanges(summary, actor) } - await $`git push` + await gitRun(["push"]) } async function pushToForkBranch(summary: string, pr: GitHubPullRequest, commit: boolean) { @@ -1145,30 +1161,28 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"` const remoteBranch = pr.headRefName if (commit) { - await $`git add .` - await $`git commit -m "${summary} - -Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"` + await gitRun(["add", "."]) + await commitChanges(summary, actor) } - await $`git push fork HEAD:${remoteBranch}` + await gitRun(["push", "fork", `HEAD:${remoteBranch}`]) } async function branchIsDirty(originalHead: string, expectedBranch: string) { console.log("Checking if branch is dirty...") // Detect if the agent switched branches during chat (e.g. created // its own branch, committed, and possibly pushed/created a PR). - const current = (await $`git rev-parse --abbrev-ref HEAD`).stdout.toString().trim() + const current = await gitText(["rev-parse", "--abbrev-ref", "HEAD"]) if (current !== expectedBranch) { console.log(`Branch changed during chat: expected ${expectedBranch}, now on ${current}`) return { dirty: true, uncommittedChanges: false, switched: true } } - const ret = await $`git status --porcelain` + const ret = await gitStatus(["status", "--porcelain"]) const status = ret.stdout.toString().trim() if (status.length > 0) { return { dirty: true, uncommittedChanges: true, switched: false } } - const head = (await $`git rev-parse HEAD`).stdout.toString().trim() + const head = await gitText(["rev-parse", "HEAD"]) return { dirty: head !== originalHead, uncommittedChanges: false, @@ -1180,11 +1194,11 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"` // Falls back to fetching from origin when local refs are missing // (common in shallow clones from actions/checkout). async function hasNewCommits(base: string, head: string) { - const result = await $`git rev-list --count ${base}..${head}`.nothrow() + const result = await gitStatus(["rev-list", "--count", `${base}..${head}`]) if (result.exitCode !== 0) { console.log(`rev-list failed, fetching origin/${base}...`) - await $`git fetch origin ${base} --depth=1`.nothrow() - const retry = await $`git rev-list --count origin/${base}..${head}`.nothrow() + await gitStatus(["fetch", "origin", base, "--depth=1"]) + const retry = await gitStatus(["rev-list", "--count", `origin/${base}..${head}`]) if (retry.exitCode !== 0) return true // assume dirty if we can't tell return parseInt(retry.stdout.toString().trim()) > 0 } diff --git a/packages/opencode/src/cli/cmd/pr.ts b/packages/opencode/src/cli/cmd/pr.ts index d617657200..ea61354741 100644 --- a/packages/opencode/src/cli/cmd/pr.ts +++ b/packages/opencode/src/cli/cmd/pr.ts @@ -1,7 +1,8 @@ import { UI } from "../ui" import { cmd } from "./cmd" import { Instance } from "@/project/instance" -import { $ } from "bun" +import { Process } from "@/util/process" +import { git } from "@/util/git" export const PrCommand = cmd({ command: "pr <number>", @@ -27,21 +28,35 @@ export const PrCommand = cmd({ UI.println(`Fetching and checking out PR #${prNumber}...`) // Use gh pr checkout with custom branch name - const result = await $`gh pr checkout ${prNumber} --branch ${localBranchName} --force`.nothrow() + const result = await Process.run( + ["gh", "pr", "checkout", `${prNumber}`, "--branch", localBranchName, "--force"], + { + nothrow: true, + }, + ) - if (result.exitCode !== 0) { + if (result.code !== 0) { UI.error(`Failed to checkout PR #${prNumber}. Make sure you have gh CLI installed and authenticated.`) process.exit(1) } // Fetch PR info for fork handling and session link detection - const prInfoResult = - await $`gh pr view ${prNumber} --json headRepository,headRepositoryOwner,isCrossRepository,headRefName,body`.nothrow() + const prInfoResult = await Process.text( + [ + "gh", + "pr", + "view", + `${prNumber}`, + "--json", + "headRepository,headRepositoryOwner,isCrossRepository,headRefName,body", + ], + { nothrow: true }, + ) let sessionId: string | undefined - if (prInfoResult.exitCode === 0) { - const prInfoText = prInfoResult.text() + if (prInfoResult.code === 0) { + const prInfoText = prInfoResult.text if (prInfoText.trim()) { const prInfo = JSON.parse(prInfoText) @@ -52,15 +67,19 @@ export const PrCommand = cmd({ const remoteName = forkOwner // Check if remote already exists - const remotes = (await $`git remote`.nothrow().text()).trim() + const remotes = (await git(["remote"], { cwd: Instance.worktree })).text().trim() if (!remotes.split("\n").includes(remoteName)) { - await $`git remote add ${remoteName} https://github.com/${forkOwner}/${forkName}.git`.nothrow() + await git(["remote", "add", remoteName, `https://github.com/${forkOwner}/${forkName}.git`], { + cwd: Instance.worktree, + }) UI.println(`Added fork remote: ${remoteName}`) } // Set upstream to the fork so pushes go there const headRefName = prInfo.headRefName - await $`git branch --set-upstream-to=${remoteName}/${headRefName} ${localBranchName}`.nothrow() + await git(["branch", `--set-upstream-to=${remoteName}/${headRefName}`, localBranchName], { + cwd: Instance.worktree, + }) } // Check for opencode session link in PR body @@ -71,9 +90,11 @@ export const PrCommand = cmd({ UI.println(`Found opencode session: ${sessionUrl}`) UI.println(`Importing session...`) - const importResult = await $`opencode import ${sessionUrl}`.nothrow() - if (importResult.exitCode === 0) { - const importOutput = importResult.text().trim() + const importResult = await Process.text(["opencode", "import", sessionUrl], { + nothrow: true, + }) + if (importResult.code === 0) { + const importOutput = importResult.text.trim() // Extract session ID from the output (format: "Imported session: <session-id>") const sessionIdMatch = importOutput.match(/Imported session: ([a-zA-Z0-9_-]+)/) if (sessionIdMatch) { diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 412ec654ff..85e13d3133 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -1,9 +1,9 @@ -import { $ } from "bun" import { platform, release } from "os" import clipboardy from "clipboardy" import { lazy } from "../../../../util/lazy.js" import { tmpdir } from "os" import path from "path" +import fs from "fs/promises" import { Filesystem } from "../../../../util/filesystem" import { Process } from "../../../../util/process" import { which } from "../../../../util/which" @@ -34,23 +34,38 @@ export namespace Clipboard { if (os === "darwin") { const tmpfile = path.join(tmpdir(), "opencode-clipboard.png") try { - await $`osascript -e 'set imageData to the clipboard as "PNGf"' -e 'set fileRef to open for access POSIX file "${tmpfile}" with write permission' -e 'set eof fileRef to 0' -e 'write imageData to fileRef' -e 'close access fileRef'` - .nothrow() - .quiet() + await Process.run( + [ + "osascript", + "-e", + 'set imageData to the clipboard as "PNGf"', + "-e", + `set fileRef to open for access POSIX file "${tmpfile}" with write permission`, + "-e", + "set eof fileRef to 0", + "-e", + "write imageData to fileRef", + "-e", + "close access fileRef", + ], + { nothrow: true }, + ) const buffer = await Filesystem.readBytes(tmpfile) return { data: buffer.toString("base64"), mime: "image/png" } } catch { } finally { - await $`rm -f "${tmpfile}"`.nothrow().quiet() + await fs.rm(tmpfile, { force: true }).catch(() => {}) } } if (os === "win32" || release().includes("WSL")) { const script = "Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }" - const base64 = await $`powershell.exe -NonInteractive -NoProfile -command "${script}"`.nothrow().text() - if (base64) { - const imageBuffer = Buffer.from(base64.trim(), "base64") + const base64 = await Process.text(["powershell.exe", "-NonInteractive", "-NoProfile", "-command", script], { + nothrow: true, + }) + if (base64.text) { + const imageBuffer = Buffer.from(base64.text.trim(), "base64") if (imageBuffer.length > 0) { return { data: imageBuffer.toString("base64"), mime: "image/png" } } @@ -58,13 +73,15 @@ export namespace Clipboard { } if (os === "linux") { - const wayland = await $`wl-paste -t image/png`.nothrow().arrayBuffer() - if (wayland && wayland.byteLength > 0) { - return { data: Buffer.from(wayland).toString("base64"), mime: "image/png" } + const wayland = await Process.run(["wl-paste", "-t", "image/png"], { nothrow: true }) + if (wayland.stdout.byteLength > 0) { + return { data: Buffer.from(wayland.stdout).toString("base64"), mime: "image/png" } } - const x11 = await $`xclip -selection clipboard -t image/png -o`.nothrow().arrayBuffer() - if (x11 && x11.byteLength > 0) { - return { data: Buffer.from(x11).toString("base64"), mime: "image/png" } + const x11 = await Process.run(["xclip", "-selection", "clipboard", "-t", "image/png", "-o"], { + nothrow: true, + }) + if (x11.stdout.byteLength > 0) { + return { data: Buffer.from(x11.stdout).toString("base64"), mime: "image/png" } } } @@ -81,7 +98,7 @@ export namespace Clipboard { console.log("clipboard: using osascript") return async (text: string) => { const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"') - await $`osascript -e 'set the clipboard to "${escaped}"'`.nothrow().quiet() + await Process.run(["osascript", "-e", `set the clipboard to "${escaped}"`], { nothrow: true }) } } diff --git a/packages/opencode/src/cli/cmd/uninstall.ts b/packages/opencode/src/cli/cmd/uninstall.ts index 3d8e7e3f75..de41f32a0d 100644 --- a/packages/opencode/src/cli/cmd/uninstall.ts +++ b/packages/opencode/src/cli/cmd/uninstall.ts @@ -3,11 +3,11 @@ import { UI } from "../ui" import * as prompts from "@clack/prompts" import { Installation } from "../../installation" import { Global } from "../../global" -import { $ } from "bun" import fs from "fs/promises" import path from "path" import os from "os" import { Filesystem } from "../../util/filesystem" +import { Process } from "../../util/process" interface UninstallArgs { keepConfig: boolean @@ -192,16 +192,13 @@ async function executeUninstall(method: Installation.Method, targets: RemovalTar const cmd = cmds[method] if (cmd) { spinner.start(`Running ${cmd.join(" ")}...`) - const result = - method === "choco" - ? await $`echo Y | choco uninstall opencode -y -r`.quiet().nothrow() - : await $`${cmd}`.quiet().nothrow() - if (result.exitCode !== 0) { - spinner.stop(`Package manager uninstall failed: exit code ${result.exitCode}`, 1) - if ( - method === "choco" && - result.stdout.toString("utf8").includes("not running from an elevated command shell") - ) { + const result = await Process.run(method === "choco" ? ["choco", "uninstall", "opencode", "-y", "-r"] : cmd, { + nothrow: true, + }) + if (result.code !== 0) { + spinner.stop(`Package manager uninstall failed: exit code ${result.code}`, 1) + const text = `${result.stdout.toString("utf8")}\n${result.stderr.toString("utf8")}` + if (method === "choco" && text.includes("not running from an elevated command shell")) { prompts.log.warn(`You may need to run '${cmd.join(" ")}' from an elevated command shell`) } else { prompts.log.warn(`You may need to run manually: ${cmd.join(" ")}`) diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 01f07c9afa..7a51ca36c7 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -1,6 +1,5 @@ import { BusEvent } from "@/bus/bus-event" import z from "zod" -import { $ } from "bun" import { formatPatch, structuredPatch } from "diff" import path from "path" import fs from "fs" @@ -11,6 +10,7 @@ import { Instance } from "../project/instance" import { Ripgrep } from "./ripgrep" import fuzzysort from "fuzzysort" import { Global } from "../global" +import { git } from "@/util/git" export namespace File { const log = Log.create({ service: "file" }) @@ -418,11 +418,11 @@ export namespace File { const project = Instance.project if (project.vcs !== "git") return [] - const diffOutput = await $`git -c core.fsmonitor=false -c core.quotepath=false diff --numstat HEAD` - .cwd(Instance.directory) - .quiet() - .nothrow() - .text() + const diffOutput = ( + await git(["-c", "core.fsmonitor=false", "-c", "core.quotepath=false", "diff", "--numstat", "HEAD"], { + cwd: Instance.directory, + }) + ).text() const changedFiles: Info[] = [] @@ -439,12 +439,14 @@ export namespace File { } } - const untrackedOutput = - await $`git -c core.fsmonitor=false -c core.quotepath=false ls-files --others --exclude-standard` - .cwd(Instance.directory) - .quiet() - .nothrow() - .text() + const untrackedOutput = ( + await git( + ["-c", "core.fsmonitor=false", "-c", "core.quotepath=false", "ls-files", "--others", "--exclude-standard"], + { + cwd: Instance.directory, + }, + ) + ).text() if (untrackedOutput.trim()) { const untrackedFiles = untrackedOutput.trim().split("\n") @@ -465,12 +467,14 @@ export namespace File { } // Get deleted files - const deletedOutput = - await $`git -c core.fsmonitor=false -c core.quotepath=false diff --name-only --diff-filter=D HEAD` - .cwd(Instance.directory) - .quiet() - .nothrow() - .text() + const deletedOutput = ( + await git( + ["-c", "core.fsmonitor=false", "-c", "core.quotepath=false", "diff", "--name-only", "--diff-filter=D", "HEAD"], + { + cwd: Instance.directory, + }, + ) + ).text() if (deletedOutput.trim()) { const deletedFiles = deletedOutput.trim().split("\n") @@ -541,16 +545,14 @@ export namespace File { const content = (await Filesystem.readText(full).catch(() => "")).trim() if (project.vcs === "git") { - let diff = await $`git -c core.fsmonitor=false diff ${file}`.cwd(Instance.directory).quiet().nothrow().text() + let diff = (await git(["-c", "core.fsmonitor=false", "diff", "--", file], { cwd: Instance.directory })).text() if (!diff.trim()) { - diff = await $`git -c core.fsmonitor=false diff --staged ${file}` - .cwd(Instance.directory) - .quiet() - .nothrow() - .text() + diff = ( + await git(["-c", "core.fsmonitor=false", "diff", "--staged", "--", file], { cwd: Instance.directory }) + ).text() } if (diff.trim()) { - const original = await $`git show HEAD:${file}`.cwd(Instance.directory).quiet().nothrow().text() + const original = (await git(["show", `HEAD:${file}`], { cwd: Instance.directory })).text() const patch = structuredPatch(file, file, original, content, "old", "new", { context: Infinity, ignoreWhitespace: true, diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 09fef453c9..b1068f5d8e 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -5,7 +5,7 @@ import fs from "fs/promises" import z from "zod" import { NamedError } from "@opencode-ai/util/error" import { lazy } from "../util/lazy" -import { $ } from "bun" + import { Filesystem } from "../util/filesystem" import { Process } from "../util/process" import { which } from "../util/which" @@ -338,7 +338,7 @@ export namespace Ripgrep { limit?: number follow?: boolean }) { - const args = [`${await filepath()}`, "--json", "--hidden", "--glob='!.git/*'"] + const args = [`${await filepath()}`, "--json", "--hidden", "--glob=!.git/*"] if (input.follow) args.push("--follow") if (input.glob) { @@ -354,14 +354,16 @@ export namespace Ripgrep { args.push("--") args.push(input.pattern) - const command = args.join(" ") - const result = await $`${{ raw: command }}`.cwd(input.cwd).quiet().nothrow() - if (result.exitCode !== 0) { + const result = await Process.text(args, { + cwd: input.cwd, + nothrow: true, + }) + if (result.code !== 0) { return [] } // Handle both Unix (\n) and Windows (\r\n) line endings - const lines = result.text().trim().split(/\r?\n/).filter(Boolean) + const lines = result.text.trim().split(/\r?\n/).filter(Boolean) // Parse JSON lines from ripgrep output return lines diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 626a746c83..537f526463 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -11,9 +11,9 @@ import { createWrapper } from "@parcel/watcher/wrapper" import { lazy } from "@/util/lazy" import { withTimeout } from "@/util/timeout" import type ParcelWatcher from "@parcel/watcher" -import { $ } from "bun" import { Flag } from "@/flag/flag" import { readdir } from "fs/promises" +import { git } from "@/util/git" const SUBSCRIBE_TIMEOUT_MS = 10_000 @@ -88,13 +88,10 @@ export namespace FileWatcher { } if (Instance.project.vcs === "git") { - const vcsDir = await $`git rev-parse --git-dir` - .quiet() - .nothrow() - .cwd(Instance.worktree) - .text() - .then((x) => path.resolve(Instance.worktree, x.trim())) - .catch(() => undefined) + const result = await git(["rev-parse", "--git-dir"], { + cwd: Instance.worktree, + }) + const vcsDir = result.exitCode === 0 ? path.resolve(Instance.worktree, result.text().trim()) : undefined if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) { const gitDirContents = await readdir(vcsDir).catch(() => []) const ignoreList = gitDirContents.filter((entry) => entry !== "HEAD") diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 47278bd562..92a3bfc796 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,11 +1,12 @@ import { BusEvent } from "@/bus/bus-event" import path from "path" -import { $ } from "bun" import z from "zod" import { NamedError } from "@opencode-ai/util/error" import { Log } from "../util/log" import { iife } from "@/util/iife" import { Flag } from "../flag/flag" +import { Process } from "@/util/process" +import { buffer } from "node:stream/consumers" declare global { const OPENCODE_VERSION: string @@ -15,6 +16,38 @@ declare global { export namespace Installation { const log = Log.create({ service: "installation" }) + async function text(cmd: string[], opts: { cwd?: string; env?: NodeJS.ProcessEnv } = {}) { + return Process.text(cmd, { + cwd: opts.cwd, + env: opts.env, + nothrow: true, + }).then((x) => x.text) + } + + async function upgradeCurl(target: string) { + const body = await fetch("https://opencode.ai/install").then((res) => { + if (!res.ok) throw new Error(res.statusText) + return res.text() + }) + const proc = Process.spawn(["bash"], { + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + env: { + ...process.env, + VERSION: target, + }, + }) + if (!proc.stdin || !proc.stdout || !proc.stderr) throw new Error("Process output not available") + proc.stdin.end(body) + const [code, stdout, stderr] = await Promise.all([proc.exited, buffer(proc.stdout), buffer(proc.stderr)]) + return { + code, + stdout, + stderr, + } + } + export type Method = Awaited<ReturnType<typeof method>> export const Event = { @@ -65,31 +98,31 @@ export namespace Installation { const checks = [ { name: "npm" as const, - command: () => $`npm list -g --depth=0`.throws(false).quiet().text(), + command: () => text(["npm", "list", "-g", "--depth=0"]), }, { name: "yarn" as const, - command: () => $`yarn global list`.throws(false).quiet().text(), + command: () => text(["yarn", "global", "list"]), }, { name: "pnpm" as const, - command: () => $`pnpm list -g --depth=0`.throws(false).quiet().text(), + command: () => text(["pnpm", "list", "-g", "--depth=0"]), }, { name: "bun" as const, - command: () => $`bun pm ls -g`.throws(false).quiet().text(), + command: () => text(["bun", "pm", "ls", "-g"]), }, { name: "brew" as const, - command: () => $`brew list --formula opencode`.throws(false).quiet().text(), + command: () => text(["brew", "list", "--formula", "opencode"]), }, { name: "scoop" as const, - command: () => $`scoop list opencode`.throws(false).quiet().text(), + command: () => text(["scoop", "list", "opencode"]), }, { name: "choco" as const, - command: () => $`choco list --limit-output opencode`.throws(false).quiet().text(), + command: () => text(["choco", "list", "--limit-output", "opencode"]), }, ] @@ -121,61 +154,70 @@ export namespace Installation { ) async function getBrewFormula() { - const tapFormula = await $`brew list --formula anomalyco/tap/opencode`.throws(false).quiet().text() + const tapFormula = await text(["brew", "list", "--formula", "anomalyco/tap/opencode"]) if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode" - const coreFormula = await $`brew list --formula opencode`.throws(false).quiet().text() + const coreFormula = await text(["brew", "list", "--formula", "opencode"]) if (coreFormula.includes("opencode")) return "opencode" return "opencode" } export async function upgrade(method: Method, target: string) { - let cmd + let result: Awaited<ReturnType<typeof upgradeCurl>> | undefined switch (method) { case "curl": - cmd = $`curl -fsSL https://opencode.ai/install | bash`.env({ - ...process.env, - VERSION: target, - }) + result = await upgradeCurl(target) break case "npm": - cmd = $`npm install -g opencode-ai@${target}` + result = await Process.run(["npm", "install", "-g", `opencode-ai@${target}`], { nothrow: true }) break case "pnpm": - cmd = $`pnpm install -g opencode-ai@${target}` + result = await Process.run(["pnpm", "install", "-g", `opencode-ai@${target}`], { nothrow: true }) break case "bun": - cmd = $`bun install -g opencode-ai@${target}` + result = await Process.run(["bun", "install", "-g", `opencode-ai@${target}`], { nothrow: true }) break case "brew": { const formula = await getBrewFormula() - if (formula.includes("/")) { - cmd = - $`brew tap anomalyco/tap && cd "$(brew --repo anomalyco/tap)" && git pull --ff-only && brew upgrade ${formula}`.env( - { - HOMEBREW_NO_AUTO_UPDATE: "1", - ...process.env, - }, - ) - break - } - cmd = $`brew upgrade ${formula}`.env({ + const env = { HOMEBREW_NO_AUTO_UPDATE: "1", ...process.env, - }) + } + if (formula.includes("/")) { + const tap = await Process.run(["brew", "tap", "anomalyco/tap"], { env, nothrow: true }) + if (tap.code !== 0) { + result = tap + break + } + const repo = await Process.text(["brew", "--repo", "anomalyco/tap"], { env, nothrow: true }) + if (repo.code !== 0) { + result = repo + break + } + const dir = repo.text.trim() + if (dir) { + const pull = await Process.run(["git", "pull", "--ff-only"], { cwd: dir, env, nothrow: true }) + if (pull.code !== 0) { + result = pull + break + } + } + } + result = await Process.run(["brew", "upgrade", formula], { env, nothrow: true }) break } + case "choco": - cmd = $`echo Y | choco upgrade opencode --version=${target}` + result = await Process.run(["choco", "upgrade", "opencode", `--version=${target}`, "-y"], { nothrow: true }) break case "scoop": - cmd = $`scoop install opencode@${target}` + result = await Process.run(["scoop", "install", `opencode@${target}`], { nothrow: true }) break default: throw new Error(`Unknown method: ${method}`) } - const result = await cmd.quiet().throws(false) - if (result.exitCode !== 0) { - const stderr = method === "choco" ? "not running from an elevated command shell" : result.stderr.toString("utf8") + if (!result || result.code !== 0) { + const stderr = + method === "choco" ? "not running from an elevated command shell" : result?.stderr.toString("utf8") || "" throw new UpgradeFailedError({ stderr: stderr, }) @@ -186,7 +228,7 @@ export namespace Installation { stdout: result.stdout.toString(), stderr: result.stderr.toString(), }) - await $`${process.execPath} --version`.nothrow().quiet().text() + await Process.text([process.execPath, "--version"], { nothrow: true }) } export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "local" @@ -199,7 +241,7 @@ export namespace Installation { if (detectedMethod === "brew") { const formula = await getBrewFormula() if (formula.includes("/")) { - const infoJson = await $`brew info --json=v2 ${formula}`.quiet().text() + const infoJson = await text(["brew", "info", "--json=v2", formula]) const info = JSON.parse(infoJson) const version = info.formulae?.[0]?.versions?.stable if (!version) throw new Error(`Could not detect version for tap formula: ${formula}`) @@ -215,7 +257,7 @@ export namespace Installation { if (detectedMethod === "npm" || detectedMethod === "bun" || detectedMethod === "pnpm") { const registry = await iife(async () => { - const r = (await $`npm config get registry`.quiet().nothrow().text()).trim() + const r = (await text(["npm", "config", "get", "registry"])).trim() const reg = r || "https://registry.npmjs.org" return reg.endsWith("/") ? reg.slice(0, -1) : reg }) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 405c672853..71f50c94b1 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -4,7 +4,6 @@ import os from "os" import { Global } from "../global" import { Log } from "../util/log" import { BunProc } from "../bun" -import { $ } from "bun" import { text } from "node:stream/consumers" import fs from "fs/promises" import { Filesystem } from "../util/filesystem" @@ -13,6 +12,7 @@ import { Flag } from "../flag/flag" import { Archive } from "../util/archive" import { Process } from "../util/process" import { which } from "../util/which" +import { Module } from "@opencode-ai/util/module" export namespace LSPServer { const log = Log.create({ service: "lsp.server" }) @@ -21,6 +21,8 @@ export namespace LSPServer { .stat(p) .then(() => true) .catch(() => false) + const run = (cmd: string[], opts: Process.RunOptions = {}) => Process.run(cmd, { ...opts, nothrow: true }) + const output = (cmd: string[], opts: Process.RunOptions = {}) => Process.text(cmd, { ...opts, nothrow: true }) export interface Handle { process: ChildProcessWithoutNullStreams @@ -97,7 +99,7 @@ export namespace LSPServer { ), extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"], async spawn(root) { - const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {}) + const tsserver = Module.resolve("typescript/lib/tsserver.js", Instance.directory) log.info("typescript server", { tsserver }) if (!tsserver) return const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], { @@ -172,7 +174,7 @@ export namespace LSPServer { root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue"], async spawn(root) { - const eslint = await Bun.resolve("eslint", Instance.directory).catch(() => {}) + const eslint = Module.resolve("eslint", Instance.directory) if (!eslint) return log.info("spawning eslint server") const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js") @@ -205,8 +207,8 @@ export namespace LSPServer { await fs.rename(extractedPath, finalPath) const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm" - await $`${npmCmd} install`.cwd(finalPath).quiet() - await $`${npmCmd} run compile`.cwd(finalPath).quiet() + await Process.run([npmCmd, "install"], { cwd: finalPath }) + await Process.run([npmCmd, "run", "compile"], { cwd: finalPath }) log.info("installed VS Code ESLint server", { serverPath }) } @@ -340,7 +342,7 @@ export namespace LSPServer { let args = ["lsp-proxy", "--stdio"] if (!bin) { - const resolved = await Bun.resolve("biome", root).catch(() => undefined) + const resolved = Module.resolve("biome", root) if (!resolved) return bin = BunProc.which() args = ["x", "biome", "lsp-proxy", "--stdio"] @@ -602,10 +604,11 @@ export namespace LSPServer { recursive: true, }) - await $`mix deps.get && mix compile && mix elixir_ls.release2 -o release` - .quiet() - .cwd(path.join(Global.Path.bin, "elixir-ls-master")) - .env({ MIX_ENV: "prod", ...process.env }) + const cwd = path.join(Global.Path.bin, "elixir-ls-master") + const env = { MIX_ENV: "prod", ...process.env } + await Process.run(["mix", "deps.get"], { cwd, env }) + await Process.run(["mix", "compile"], { cwd, env }) + await Process.run(["mix", "elixir_ls.release2", "-o", "release"], { cwd, env }) log.info(`installed elixir-ls`, { path: elixirLsPath, @@ -706,7 +709,7 @@ export namespace LSPServer { }) if (!ok) return } else { - await $`tar -xf ${tempPath}`.cwd(Global.Path.bin).quiet().nothrow() + await run(["tar", "-xf", tempPath], { cwd: Global.Path.bin }) } await fs.rm(tempPath, { force: true }) @@ -719,7 +722,7 @@ export namespace LSPServer { } if (platform !== "win32") { - await $`chmod +x ${bin}`.quiet().nothrow() + await fs.chmod(bin, 0o755).catch(() => {}) } log.info(`installed zls`, { bin }) @@ -831,11 +834,11 @@ export namespace LSPServer { // This is specific to macOS where sourcekit-lsp is typically installed with Xcode if (!which("xcrun")) return - const lspLoc = await $`xcrun --find sourcekit-lsp`.quiet().nothrow() + const lspLoc = await output(["xcrun", "--find", "sourcekit-lsp"]) - if (lspLoc.exitCode !== 0) return + if (lspLoc.code !== 0) return - const bin = lspLoc.text().trim() + const bin = lspLoc.text.trim() return { process: spawn(bin, { @@ -1010,7 +1013,7 @@ export namespace LSPServer { if (!ok) return } if (tar) { - await $`tar -xf ${archive}`.cwd(Global.Path.bin).quiet().nothrow() + await run(["tar", "-xf", archive], { cwd: Global.Path.bin }) } await fs.rm(archive, { force: true }) @@ -1021,7 +1024,7 @@ export namespace LSPServer { } if (platform !== "win32") { - await $`chmod +x ${bin}`.quiet().nothrow() + await fs.chmod(bin, 0o755).catch(() => {}) } await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {}) @@ -1082,7 +1085,7 @@ export namespace LSPServer { extensions: [".astro"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), async spawn(root) { - const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {}) + const tsserver = Module.resolve("typescript/lib/tsserver.js", Instance.directory) if (!tsserver) { log.info("typescript not found, required for Astro language server") return @@ -1161,13 +1164,10 @@ export namespace LSPServer { log.error("Java 21 or newer is required to run the JDTLS. Please install it first.") return } - const javaMajorVersion = await $`java -version` - .quiet() - .nothrow() - .then(({ stderr }) => { - const m = /"(\d+)\.\d+\.\d+"/.exec(stderr.toString()) - return !m ? undefined : parseInt(m[1]) - }) + const javaMajorVersion = await run(["java", "-version"]).then((result) => { + const m = /"(\d+)\.\d+\.\d+"/.exec(result.stderr.toString()) + return !m ? undefined : parseInt(m[1]) + }) if (javaMajorVersion == null || javaMajorVersion < 21) { log.error("JDTLS requires at least Java 21.") return @@ -1184,27 +1184,27 @@ export namespace LSPServer { const archiveName = "release.tar.gz" log.info("Downloading JDTLS archive", { url: releaseURL, dest: distPath }) - const curlResult = await $`curl -L -o ${archiveName} '${releaseURL}'`.cwd(distPath).quiet().nothrow() - if (curlResult.exitCode !== 0) { - log.error("Failed to download JDTLS", { exitCode: curlResult.exitCode, stderr: curlResult.stderr.toString() }) + const download = await fetch(releaseURL) + if (!download.ok || !download.body) { + log.error("Failed to download JDTLS", { status: download.status, statusText: download.statusText }) return } + await Filesystem.writeStream(path.join(distPath, archiveName), download.body) log.info("Extracting JDTLS archive") - const tarResult = await $`tar -xzf ${archiveName}`.cwd(distPath).quiet().nothrow() - if (tarResult.exitCode !== 0) { - log.error("Failed to extract JDTLS", { exitCode: tarResult.exitCode, stderr: tarResult.stderr.toString() }) + const tarResult = await run(["tar", "-xzf", archiveName], { cwd: distPath }) + if (tarResult.code !== 0) { + log.error("Failed to extract JDTLS", { exitCode: tarResult.code, stderr: tarResult.stderr.toString() }) return } await fs.rm(path.join(distPath, archiveName), { force: true }) log.info("JDTLS download and extraction completed") } - const jarFileName = await $`ls org.eclipse.equinox.launcher_*.jar` - .cwd(launcherDir) - .quiet() - .nothrow() - .then(({ stdout }) => stdout.toString().trim()) + const jarFileName = + (await fs.readdir(launcherDir).catch(() => [])) + .find((item) => /^org\.eclipse\.equinox\.launcher_.*\.jar$/.test(item)) + ?.trim() ?? "" const launcherJar = path.join(launcherDir, jarFileName) if (!(await pathExists(launcherJar))) { log.error(`Failed to locate the JDTLS launcher module in the installed directory: ${distPath}.`) @@ -1317,7 +1317,15 @@ export namespace LSPServer { await fs.mkdir(distPath, { recursive: true }) const archivePath = path.join(distPath, "kotlin-ls.zip") - await $`curl -L -o '${archivePath}' '${releaseURL}'`.quiet().nothrow() + const download = await fetch(releaseURL) + if (!download.ok || !download.body) { + log.error("Failed to download Kotlin Language Server", { + status: download.status, + statusText: download.statusText, + }) + return + } + await Filesystem.writeStream(archivePath, download.body) const ok = await Archive.extractZip(archivePath, distPath) .then(() => true) .catch((error) => { @@ -1327,7 +1335,7 @@ export namespace LSPServer { if (!ok) return await fs.rm(archivePath, { force: true }) if (process.platform !== "win32") { - await $`chmod +x ${launcherScript}`.quiet().nothrow() + await fs.chmod(launcherScript, 0o755).catch(() => {}) } log.info("Installed Kotlin Language Server", { path: launcherScript }) } @@ -1491,10 +1499,9 @@ export namespace LSPServer { }) if (!ok) return } else { - const ok = await $`tar -xzf ${tempPath} -C ${installDir}` - .quiet() - .then(() => true) - .catch((error) => { + const ok = await run(["tar", "-xzf", tempPath, "-C", installDir]) + .then((result) => result.code === 0) + .catch((error: unknown) => { log.error("Failed to extract lua-language-server archive", { error }) return false }) @@ -1512,11 +1519,15 @@ export namespace LSPServer { } if (platform !== "win32") { - const ok = await $`chmod +x ${bin}`.quiet().catch((error) => { - log.error("Failed to set executable permission for lua-language-server binary", { - error, + const ok = await fs + .chmod(bin, 0o755) + .then(() => true) + .catch((error: unknown) => { + log.error("Failed to set executable permission for lua-language-server binary", { + error, + }) + return false }) - }) if (!ok) return } @@ -1730,7 +1741,7 @@ export namespace LSPServer { } if (platform !== "win32") { - await $`chmod +x ${bin}`.quiet().nothrow() + await fs.chmod(bin, 0o755).catch(() => {}) } log.info(`installed terraform-ls`, { bin }) @@ -1813,7 +1824,7 @@ export namespace LSPServer { if (!ok) return } if (ext === "tar.gz") { - await $`tar -xzf ${tempPath}`.cwd(Global.Path.bin).quiet().nothrow() + await run(["tar", "-xzf", tempPath], { cwd: Global.Path.bin }) } await fs.rm(tempPath, { force: true }) @@ -1826,7 +1837,7 @@ export namespace LSPServer { } if (platform !== "win32") { - await $`chmod +x ${bin}`.quiet().nothrow() + await fs.chmod(bin, 0o755).catch(() => {}) } log.info("installed texlab", { bin }) @@ -2018,7 +2029,7 @@ export namespace LSPServer { }) if (!ok) return } else { - await $`tar -xzf ${tempPath} --strip-components=1`.cwd(Global.Path.bin).quiet().nothrow() + await run(["tar", "-xzf", tempPath, "--strip-components=1"], { cwd: Global.Path.bin }) } await fs.rm(tempPath, { force: true }) @@ -2031,7 +2042,7 @@ export namespace LSPServer { } if (platform !== "win32") { - await $`chmod +x ${bin}`.quiet().nothrow() + await fs.chmod(bin, 0o755).catch(() => {}) } log.info("installed tinymist", { bin }) diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index e434b5f8c3..34d5905431 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -1,11 +1,11 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" -import { $ } from "bun" import path from "path" import z from "zod" import { Log } from "@/util/log" import { Instance } from "./instance" import { FileWatcher } from "@/file/watcher" +import { git } from "@/util/git" const log = Log.create({ service: "vcs" }) @@ -29,13 +29,13 @@ export namespace Vcs { export type Info = z.infer<typeof Info> async function currentBranch() { - return $`git rev-parse --abbrev-ref HEAD` - .quiet() - .nothrow() - .cwd(Instance.worktree) - .text() - .then((x) => x.trim()) - .catch(() => undefined) + const result = await git(["rev-parse", "--abbrev-ref", "HEAD"], { + cwd: Instance.worktree, + }) + if (result.exitCode !== 0) return + const text = result.text().trim() + if (!text) return + return text } const state = Instance.state( diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 1acbdba092..848f2694e0 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -1,4 +1,3 @@ -import { $ } from "bun" import path from "path" import fs from "fs/promises" import { Filesystem } from "../util/filesystem" @@ -9,12 +8,17 @@ import z from "zod" import { Config } from "../config/config" import { Instance } from "../project/instance" import { Scheduler } from "../scheduler" +import { Process } from "@/util/process" export namespace Snapshot { const log = Log.create({ service: "snapshot" }) const hour = 60 * 60 * 1000 const prune = "7.days" + function args(git: string, cmd: string[]) { + return ["--git-dir", git, "--work-tree", Instance.worktree, ...cmd] + } + export function init() { Scheduler.register({ id: "snapshot.cleanup", @@ -34,13 +38,13 @@ export namespace Snapshot { .then(() => true) .catch(() => false) if (!exists) return - const result = await $`git --git-dir ${git} --work-tree ${Instance.worktree} gc --prune=${prune}` - .quiet() - .cwd(Instance.directory) - .nothrow() - if (result.exitCode !== 0) { + const result = await Process.run(["git", ...args(git, ["gc", `--prune=${prune}`])], { + cwd: Instance.directory, + nothrow: true, + }) + if (result.code !== 0) { log.warn("cleanup failed", { - exitCode: result.exitCode, + exitCode: result.code, stderr: result.stderr.toString(), stdout: result.stdout.toString(), }) @@ -55,27 +59,27 @@ export namespace Snapshot { if (cfg.snapshot === false) return const git = gitdir() if (await fs.mkdir(git, { recursive: true })) { - await $`git init` - .env({ + await Process.run(["git", "init"], { + env: { ...process.env, GIT_DIR: git, GIT_WORK_TREE: Instance.worktree, - }) - .quiet() - .nothrow() + }, + nothrow: true, + }) + // Configure git to not convert line endings on Windows - await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow() - await $`git --git-dir ${git} config core.longpaths true`.quiet().nothrow() - await $`git --git-dir ${git} config core.symlinks true`.quiet().nothrow() - await $`git --git-dir ${git} config core.fsmonitor false`.quiet().nothrow() + await Process.run(["git", "--git-dir", git, "config", "core.autocrlf", "false"], { nothrow: true }) + await Process.run(["git", "--git-dir", git, "config", "core.longpaths", "true"], { nothrow: true }) + await Process.run(["git", "--git-dir", git, "config", "core.symlinks", "true"], { nothrow: true }) + await Process.run(["git", "--git-dir", git, "config", "core.fsmonitor", "false"], { nothrow: true }) log.info("initialized") } await add(git) - const hash = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree` - .quiet() - .cwd(Instance.directory) - .nothrow() - .text() + const hash = await Process.text(["git", ...args(git, ["write-tree"])], { + cwd: Instance.directory, + nothrow: true, + }).then((x) => x.text) log.info("tracking", { hash, cwd: Instance.directory, git }) return hash.trim() } @@ -89,19 +93,32 @@ export namespace Snapshot { export async function patch(hash: string): Promise<Patch> { const git = gitdir() await add(git) - const result = - await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-only ${hash} -- .` - .quiet() - .cwd(Instance.directory) - .nothrow() + const result = await Process.text( + [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + "-c", + "core.quotepath=false", + ...args(git, ["diff", "--no-ext-diff", "--name-only", hash, "--", "."]), + ], + { + cwd: Instance.directory, + nothrow: true, + }, + ) // If git diff fails, return empty patch - if (result.exitCode !== 0) { - log.warn("failed to get diff", { hash, exitCode: result.exitCode }) + if (result.code !== 0) { + log.warn("failed to get diff", { hash, exitCode: result.code }) return { hash, files: [] } } - const files = result.text() + const files = result.text return { hash, files: files @@ -116,20 +133,37 @@ export namespace Snapshot { export async function restore(snapshot: string) { log.info("restore", { commit: snapshot }) const git = gitdir() - const result = - await $`git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} read-tree ${snapshot} && git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} checkout-index -a -f` - .quiet() - .cwd(Instance.worktree) - .nothrow() - - if (result.exitCode !== 0) { + const result = await Process.run( + ["git", "-c", "core.longpaths=true", "-c", "core.symlinks=true", ...args(git, ["read-tree", snapshot])], + { + cwd: Instance.worktree, + nothrow: true, + }, + ) + if (result.code === 0) { + const checkout = await Process.run( + ["git", "-c", "core.longpaths=true", "-c", "core.symlinks=true", ...args(git, ["checkout-index", "-a", "-f"])], + { + cwd: Instance.worktree, + nothrow: true, + }, + ) + if (checkout.code === 0) return log.error("failed to restore snapshot", { snapshot, - exitCode: result.exitCode, - stderr: result.stderr.toString(), - stdout: result.stdout.toString(), + exitCode: checkout.code, + stderr: checkout.stderr.toString(), + stdout: checkout.stdout.toString(), }) + return } + + log.error("failed to restore snapshot", { + snapshot, + exitCode: result.code, + stderr: result.stderr.toString(), + stdout: result.stdout.toString(), + }) } export async function revert(patches: Patch[]) { @@ -139,19 +173,37 @@ export namespace Snapshot { for (const file of item.files) { if (files.has(file)) continue log.info("reverting", { file, hash: item.hash }) - const result = - await $`git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} checkout ${item.hash} -- ${file}` - .quiet() - .cwd(Instance.worktree) - .nothrow() - if (result.exitCode !== 0) { + const result = await Process.run( + [ + "git", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + ...args(git, ["checkout", item.hash, "--", file]), + ], + { + cwd: Instance.worktree, + nothrow: true, + }, + ) + if (result.code !== 0) { const relativePath = path.relative(Instance.worktree, file) - const checkTree = - await $`git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} ls-tree ${item.hash} -- ${relativePath}` - .quiet() - .cwd(Instance.worktree) - .nothrow() - if (checkTree.exitCode === 0 && checkTree.text().trim()) { + const checkTree = await Process.text( + [ + "git", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + ...args(git, ["ls-tree", item.hash, "--", relativePath]), + ], + { + cwd: Instance.worktree, + nothrow: true, + }, + ) + if (checkTree.code === 0 && checkTree.text.trim()) { log.info("file existed in snapshot but checkout failed, keeping", { file, }) @@ -168,23 +220,36 @@ export namespace Snapshot { export async function diff(hash: string) { const git = gitdir() await add(git) - const result = - await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .` - .quiet() - .cwd(Instance.worktree) - .nothrow() + const result = await Process.text( + [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + "-c", + "core.quotepath=false", + ...args(git, ["diff", "--no-ext-diff", hash, "--", "."]), + ], + { + cwd: Instance.worktree, + nothrow: true, + }, + ) - if (result.exitCode !== 0) { + if (result.code !== 0) { log.warn("failed to get diff", { hash, - exitCode: result.exitCode, + exitCode: result.code, stderr: result.stderr.toString(), stdout: result.stdout.toString(), }) return "" } - return result.text().trim() + return result.text.trim() } export const FileDiff = z @@ -205,12 +270,24 @@ export namespace Snapshot { const result: FileDiff[] = [] const status = new Map<string, "added" | "deleted" | "modified">() - const statuses = - await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-status --no-renames ${from} ${to} -- .` - .quiet() - .cwd(Instance.directory) - .nothrow() - .text() + const statuses = await Process.text( + [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + "-c", + "core.quotepath=false", + ...args(git, ["diff", "--no-ext-diff", "--name-status", "--no-renames", from, to, "--", "."]), + ], + { + cwd: Instance.directory, + nothrow: true, + }, + ).then((x) => x.text) for (const line of statuses.trim().split("\n")) { if (!line) continue @@ -220,26 +297,57 @@ export namespace Snapshot { status.set(file, kind) } - for await (const line of $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --no-renames --numstat ${from} ${to} -- .` - .quiet() - .cwd(Instance.directory) - .nothrow() - .lines()) { + for (const line of await Process.lines( + [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + "-c", + "core.quotepath=false", + ...args(git, ["diff", "--no-ext-diff", "--no-renames", "--numstat", from, to, "--", "."]), + ], + { + cwd: Instance.directory, + nothrow: true, + }, + )) { if (!line) continue const [additions, deletions, file] = line.split("\t") const isBinaryFile = additions === "-" && deletions === "-" const before = isBinaryFile ? "" - : await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} show ${from}:${file}` - .quiet() - .nothrow() - .text() + : await Process.text( + [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + ...args(git, ["show", `${from}:${file}`]), + ], + { nothrow: true }, + ).then((x) => x.text) const after = isBinaryFile ? "" - : await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} show ${to}:${file}` - .quiet() - .nothrow() - .text() + : await Process.text( + [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + ...args(git, ["show", `${to}:${file}`]), + ], + { nothrow: true }, + ).then((x) => x.text) const added = isBinaryFile ? 0 : parseInt(additions) const deleted = isBinaryFile ? 0 : parseInt(deletions) result.push({ @@ -261,10 +369,22 @@ export namespace Snapshot { async function add(git: string) { await syncExclude(git) - await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} add .` - .quiet() - .cwd(Instance.directory) - .nothrow() + await Process.run( + [ + "git", + "-c", + "core.autocrlf=false", + "-c", + "core.longpaths=true", + "-c", + "core.symlinks=true", + ...args(git, ["add", "."]), + ], + { + cwd: Instance.directory, + nothrow: true, + }, + ) } async function syncExclude(git: string) { @@ -281,11 +401,10 @@ export namespace Snapshot { } async function excludes() { - const file = await $`git rev-parse --path-format=absolute --git-path info/exclude` - .quiet() - .cwd(Instance.worktree) - .nothrow() - .text() + const file = await Process.text(["git", "rev-parse", "--path-format=absolute", "--git-path", "info/exclude"], { + cwd: Instance.worktree, + nothrow: true, + }).then((x) => x.text) if (!file.trim()) return const exists = await fs .stat(file.trim()) diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index a78ff04f43..a78607cdfd 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -5,10 +5,10 @@ import { Global } from "../global" import { Filesystem } from "../util/filesystem" import { lazy } from "../util/lazy" import { Lock } from "../util/lock" -import { $ } from "bun" import { NamedError } from "@opencode-ai/util/error" import z from "zod" import { Glob } from "../util/glob" +import { git } from "@/util/git" export namespace Storage { const log = Log.create({ service: "storage" }) @@ -49,18 +49,15 @@ export namespace Storage { } if (!worktree) continue if (!(await Filesystem.isDir(worktree))) continue - const [id] = await $`git rev-list --max-parents=0 --all` - .quiet() - .nothrow() - .cwd(worktree) + const result = await git(["rev-list", "--max-parents=0", "--all"], { + cwd: worktree, + }) + const [id] = result .text() - .then((x) => - x - .split("\n") - .filter(Boolean) - .map((x) => x.trim()) - .toSorted(), - ) + .split("\n") + .filter(Boolean) + .map((x) => x.trim()) + .toSorted() if (!id) continue projectID = id diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 0751f789b7..3be2734863 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -7,8 +7,8 @@ import { Log } from "../util/log" import { Instance } from "../project/instance" import { lazy } from "@/util/lazy" import { Language } from "web-tree-sitter" +import fs from "fs/promises" -import { $ } from "bun" import { Filesystem } from "@/util/filesystem" import { fileURLToPath } from "url" import { Flag } from "@/flag/flag.ts" @@ -116,12 +116,7 @@ export const BashTool = Tool.define("bash", async () => { if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown", "cat"].includes(command[0])) { for (const arg of command.slice(1)) { if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue - const resolved = await $`realpath ${arg}` - .cwd(cwd) - .quiet() - .nothrow() - .text() - .then((x) => x.trim()) + const resolved = await fs.realpath(path.resolve(cwd, arg)).catch(() => "") log.info("resolved path", { arg, resolved }) if (resolved) { const normalized = diff --git a/packages/opencode/src/util/archive.ts b/packages/opencode/src/util/archive.ts index 34a1738a8c..f65ceba547 100644 --- a/packages/opencode/src/util/archive.ts +++ b/packages/opencode/src/util/archive.ts @@ -1,5 +1,5 @@ -import { $ } from "bun" import path from "path" +import { Process } from "./process" export namespace Archive { export async function extractZip(zipPath: string, destDir: string) { @@ -8,9 +8,10 @@ export namespace Archive { const winDestDir = path.resolve(destDir) // $global:ProgressPreference suppresses PowerShell's blue progress bar popup const cmd = `$global:ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path '${winZipPath}' -DestinationPath '${winDestDir}' -Force` - await $`powershell -NoProfile -NonInteractive -Command ${cmd}`.quiet() - } else { - await $`unzip -o -q ${zipPath} -d ${destDir}`.quiet() + await Process.run(["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd]) + return } + + await Process.run(["unzip", "-o", "-q", zipPath, "-d", destDir]) } } diff --git a/packages/opencode/src/util/process.ts b/packages/opencode/src/util/process.ts index 71f001a86a..473ee27dc9 100644 --- a/packages/opencode/src/util/process.ts +++ b/packages/opencode/src/util/process.ts @@ -25,6 +25,10 @@ export namespace Process { stderr: Buffer } + export interface TextResult extends Result { + text: string + } + export class RunFailedError extends Error { readonly cmd: string[] readonly code: number @@ -114,13 +118,33 @@ export namespace Process { if (!proc.stdout || !proc.stderr) throw new Error("Process output not available") - const [code, stdout, stderr] = await Promise.all([proc.exited, buffer(proc.stdout), buffer(proc.stderr)]) - const out = { - code, - stdout, - stderr, - } + const out = await Promise.all([proc.exited, buffer(proc.stdout), buffer(proc.stderr)]) + .then(([code, stdout, stderr]) => ({ + code, + stdout, + stderr, + })) + .catch((err: unknown) => { + if (!opts.nothrow) throw err + return { + code: 1, + stdout: Buffer.alloc(0), + stderr: Buffer.from(err instanceof Error ? err.message : String(err)), + } + }) if (out.code === 0 || opts.nothrow) return out throw new RunFailedError(cmd, out.code, out.stdout, out.stderr) } + + export async function text(cmd: string[], opts: RunOptions = {}): Promise<TextResult> { + const out = await run(cmd, opts) + return { + ...out, + text: out.stdout.toString(), + } + } + + export async function lines(cmd: string[], opts: RunOptions = {}): Promise<string[]> { + return (await text(cmd, opts)).text.split(/\r?\n/).filter(Boolean) + } } diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 15efcea386..8b5accc333 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -1,4 +1,3 @@ -import { $ } from "bun" import fs from "fs/promises" import path from "path" import z from "zod" @@ -11,6 +10,8 @@ import { Database, eq } from "../storage/db" import { ProjectTable } from "../project/project.sql" import { fn } from "../util/fn" import { Log } from "../util/log" +import { Process } from "../util/process" +import { git } from "../util/git" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" @@ -248,14 +249,14 @@ export namespace Worktree { } async function sweep(root: string) { - const first = await $`git clean -ffdx`.quiet().nothrow().cwd(root) + const first = await git(["clean", "-ffdx"], { cwd: root }) if (first.exitCode === 0) return first const entries = failed(first) if (!entries.length) return first await prune(root, entries) - return $`git clean -ffdx`.quiet().nothrow().cwd(root) + return git(["clean", "-ffdx"], { cwd: root }) } async function canonical(input: string) { @@ -274,7 +275,9 @@ export namespace Worktree { if (await exists(directory)) continue const ref = `refs/heads/${branch}` - const branchCheck = await $`git show-ref --verify --quiet ${ref}`.quiet().nothrow().cwd(Instance.worktree) + const branchCheck = await git(["show-ref", "--verify", "--quiet", ref], { + cwd: Instance.worktree, + }) if (branchCheck.exitCode === 0) continue return Info.parse({ name, branch, directory }) @@ -285,9 +288,9 @@ export namespace Worktree { async function runStartCommand(directory: string, cmd: string) { if (process.platform === "win32") { - return $`cmd /c ${cmd}`.nothrow().cwd(directory) + return Process.run(["cmd", "/c", cmd], { cwd: directory, nothrow: true }) } - return $`bash -lc ${cmd}`.nothrow().cwd(directory) + return Process.run(["bash", "-lc", cmd], { cwd: directory, nothrow: true }) } type StartKind = "project" | "worktree" @@ -297,7 +300,7 @@ export namespace Worktree { if (!text) return true const ran = await runStartCommand(directory, text) - if (ran.exitCode === 0) return true + if (ran.code === 0) return true log.error("worktree start command failed", { kind, @@ -344,10 +347,9 @@ export namespace Worktree { } export async function createFromInfo(info: Info, startCommand?: string) { - const created = await $`git worktree add --no-checkout -b ${info.branch} ${info.directory}` - .quiet() - .nothrow() - .cwd(Instance.worktree) + const created = await git(["worktree", "add", "--no-checkout", "-b", info.branch, info.directory], { + cwd: Instance.worktree, + }) if (created.exitCode !== 0) { throw new CreateFailedError({ message: errorText(created) || "Failed to create git worktree" }) } @@ -359,7 +361,7 @@ export namespace Worktree { return () => { const start = async () => { - const populated = await $`git reset --hard`.quiet().nothrow().cwd(info.directory) + const populated = await git(["reset", "--hard"], { cwd: info.directory }) if (populated.exitCode !== 0) { const message = errorText(populated) || "Failed to populate worktree" log.error("worktree checkout failed", { directory: info.directory, message }) @@ -476,10 +478,10 @@ export namespace Worktree { const stop = async (target: string) => { if (!(await exists(target))) return - await $`git fsmonitor--daemon stop`.quiet().nothrow().cwd(target) + await git(["fsmonitor--daemon", "stop"], { cwd: target }) } - const list = await $`git worktree list --porcelain`.quiet().nothrow().cwd(Instance.worktree) + const list = await git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree }) if (list.exitCode !== 0) { throw new RemoveFailedError({ message: errorText(list) || "Failed to read git worktrees" }) } @@ -496,9 +498,11 @@ export namespace Worktree { } await stop(entry.path) - const removed = await $`git worktree remove --force ${entry.path}`.quiet().nothrow().cwd(Instance.worktree) + const removed = await git(["worktree", "remove", "--force", entry.path], { + cwd: Instance.worktree, + }) if (removed.exitCode !== 0) { - const next = await $`git worktree list --porcelain`.quiet().nothrow().cwd(Instance.worktree) + const next = await git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree }) if (next.exitCode !== 0) { throw new RemoveFailedError({ message: errorText(removed) || errorText(next) || "Failed to remove git worktree", @@ -515,7 +519,7 @@ export namespace Worktree { const branch = entry.branch?.replace(/^refs\/heads\//, "") if (branch) { - const deleted = await $`git branch -D ${branch}`.quiet().nothrow().cwd(Instance.worktree) + const deleted = await git(["branch", "-D", branch], { cwd: Instance.worktree }) if (deleted.exitCode !== 0) { throw new RemoveFailedError({ message: errorText(deleted) || "Failed to delete worktree branch" }) } @@ -535,7 +539,7 @@ export namespace Worktree { throw new ResetFailedError({ message: "Cannot reset the primary workspace" }) } - const list = await $`git worktree list --porcelain`.quiet().nothrow().cwd(Instance.worktree) + const list = await git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree }) if (list.exitCode !== 0) { throw new ResetFailedError({ message: errorText(list) || "Failed to read git worktrees" }) } @@ -568,7 +572,7 @@ export namespace Worktree { throw new ResetFailedError({ message: "Worktree not found" }) } - const remoteList = await $`git remote`.quiet().nothrow().cwd(Instance.worktree) + const remoteList = await git(["remote"], { cwd: Instance.worktree }) if (remoteList.exitCode !== 0) { throw new ResetFailedError({ message: errorText(remoteList) || "Failed to list git remotes" }) } @@ -587,18 +591,19 @@ export namespace Worktree { : "" const remoteHead = remote - ? await $`git symbolic-ref refs/remotes/${remote}/HEAD`.quiet().nothrow().cwd(Instance.worktree) + ? await git(["symbolic-ref", `refs/remotes/${remote}/HEAD`], { cwd: Instance.worktree }) : { exitCode: 1, stdout: undefined, stderr: undefined } const remoteRef = remoteHead.exitCode === 0 ? outputText(remoteHead.stdout) : "" const remoteTarget = remoteRef ? remoteRef.replace(/^refs\/remotes\//, "") : "" const remoteBranch = remote && remoteTarget.startsWith(`${remote}/`) ? remoteTarget.slice(`${remote}/`.length) : "" - const mainCheck = await $`git show-ref --verify --quiet refs/heads/main`.quiet().nothrow().cwd(Instance.worktree) - const masterCheck = await $`git show-ref --verify --quiet refs/heads/master` - .quiet() - .nothrow() - .cwd(Instance.worktree) + const mainCheck = await git(["show-ref", "--verify", "--quiet", "refs/heads/main"], { + cwd: Instance.worktree, + }) + const masterCheck = await git(["show-ref", "--verify", "--quiet", "refs/heads/master"], { + cwd: Instance.worktree, + }) const localBranch = mainCheck.exitCode === 0 ? "main" : masterCheck.exitCode === 0 ? "master" : "" const target = remoteBranch ? `${remote}/${remoteBranch}` : localBranch @@ -607,7 +612,7 @@ export namespace Worktree { } if (remoteBranch) { - const fetch = await $`git fetch ${remote} ${remoteBranch}`.quiet().nothrow().cwd(Instance.worktree) + const fetch = await git(["fetch", remote, remoteBranch], { cwd: Instance.worktree }) if (fetch.exitCode !== 0) { throw new ResetFailedError({ message: errorText(fetch) || `Failed to fetch ${target}` }) } @@ -619,7 +624,7 @@ export namespace Worktree { const worktreePath = entry.path - const resetToTarget = await $`git reset --hard ${target}`.quiet().nothrow().cwd(worktreePath) + const resetToTarget = await git(["reset", "--hard", target], { cwd: worktreePath }) if (resetToTarget.exitCode !== 0) { throw new ResetFailedError({ message: errorText(resetToTarget) || "Failed to reset worktree to target" }) } @@ -629,22 +634,26 @@ export namespace Worktree { throw new ResetFailedError({ message: errorText(clean) || "Failed to clean worktree" }) } - const update = await $`git submodule update --init --recursive --force`.quiet().nothrow().cwd(worktreePath) + const update = await git(["submodule", "update", "--init", "--recursive", "--force"], { cwd: worktreePath }) if (update.exitCode !== 0) { throw new ResetFailedError({ message: errorText(update) || "Failed to update submodules" }) } - const subReset = await $`git submodule foreach --recursive git reset --hard`.quiet().nothrow().cwd(worktreePath) + const subReset = await git(["submodule", "foreach", "--recursive", "git", "reset", "--hard"], { + cwd: worktreePath, + }) if (subReset.exitCode !== 0) { throw new ResetFailedError({ message: errorText(subReset) || "Failed to reset submodules" }) } - const subClean = await $`git submodule foreach --recursive git clean -fdx`.quiet().nothrow().cwd(worktreePath) + const subClean = await git(["submodule", "foreach", "--recursive", "git", "clean", "-fdx"], { + cwd: worktreePath, + }) if (subClean.exitCode !== 0) { throw new ResetFailedError({ message: errorText(subClean) || "Failed to clean submodules" }) } - const status = await $`git -c core.fsmonitor=false status --porcelain=v1`.quiet().nothrow().cwd(worktreePath) + const status = await git(["-c", "core.fsmonitor=false", "status", "--porcelain=v1"], { cwd: worktreePath }) if (status.exitCode !== 0) { throw new ResetFailedError({ message: errorText(status) || "Failed to read git status" }) } diff --git a/packages/opencode/test/util/module.test.ts b/packages/opencode/test/util/module.test.ts new file mode 100644 index 0000000000..738b4a785b --- /dev/null +++ b/packages/opencode/test/util/module.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { Module } from "@opencode-ai/util/module" +import { Filesystem } from "../../src/util/filesystem" +import { tmpdir } from "../fixture/fixture" + +describe("util.module", () => { + test("resolves package subpaths from the provided dir", async () => { + await using tmp = await tmpdir() + const root = path.join(tmp.path, "proj") + const file = path.join(root, "node_modules/typescript/lib/tsserver.js") + await Filesystem.write(file, "export {}\n") + await Filesystem.writeJson(path.join(root, "node_modules/typescript/package.json"), { name: "typescript" }) + + expect(Module.resolve("typescript/lib/tsserver.js", root)).toBe(file) + }) + + test("resolves packages through ancestor node_modules", async () => { + await using tmp = await tmpdir() + const root = path.join(tmp.path, "proj") + const cwd = path.join(root, "apps/web") + const file = path.join(root, "node_modules/eslint/lib/api.js") + await Filesystem.write(file, "export {}\n") + await Filesystem.writeJson(path.join(root, "node_modules/eslint/package.json"), { + name: "eslint", + main: "lib/api.js", + }) + await Filesystem.write(path.join(cwd, ".keep"), "") + + expect(Module.resolve("eslint", cwd)).toBe(file) + }) + + test("resolves relative to the provided dir", async () => { + await using tmp = await tmpdir() + const a = path.join(tmp.path, "a") + const b = path.join(tmp.path, "b") + const left = path.join(a, "node_modules/biome/index.js") + const right = path.join(b, "node_modules/biome/index.js") + await Filesystem.write(left, "export {}\n") + await Filesystem.write(right, "export {}\n") + await Filesystem.writeJson(path.join(a, "node_modules/biome/package.json"), { + name: "biome", + main: "index.js", + }) + await Filesystem.writeJson(path.join(b, "node_modules/biome/package.json"), { + name: "biome", + main: "index.js", + }) + + expect(Module.resolve("biome", a)).toBe(left) + expect(Module.resolve("biome", b)).toBe(right) + expect(Module.resolve("biome", a)).not.toBe(Module.resolve("biome", b)) + }) + + test("returns undefined when resolution fails", async () => { + await using tmp = await tmpdir() + expect(Module.resolve("missing-package", tmp.path)).toBeUndefined() + }) +}) diff --git a/packages/util/src/module.ts b/packages/util/src/module.ts new file mode 100644 index 0000000000..6ed3b23d7a --- /dev/null +++ b/packages/util/src/module.ts @@ -0,0 +1,10 @@ +import { createRequire } from "node:module" +import path from "node:path" + +export namespace Module { + export function resolve(id: string, dir: string) { + try { + return createRequire(path.join(dir, "package.json")).resolve(id) + } catch {} + } +} From b312928e9f9149aaacf1cbb9af96399c1672c334 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf <sda@fstab.net> Date: Mon, 9 Mar 2026 15:22:38 -0400 Subject: [PATCH 027/200] fix(tui): wait for model store before auto-submitting --prompt (#7476) --- .../opencode/src/cli/cmd/tui/routes/home.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index c011f6c624..24ea8f3b31 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -1,5 +1,5 @@ import { Prompt, type PromptRef } from "@tui/component/prompt" -import { createMemo, Match, onMount, Show, Switch } from "solid-js" +import { createEffect, createMemo, Match, on, onMount, Show, Switch } from "solid-js" import { useTheme } from "@tui/context/theme" import { useKeybind } from "@tui/context/keybind" import { Logo } from "../component/logo" @@ -14,6 +14,7 @@ import { usePromptRef } from "../context/prompt" import { Installation } from "@/installation" import { useKV } from "../context/kv" import { useCommandDialog } from "../component/dialog-command" +import { useLocal } from "../context/local" // TODO: what is the best way to do this? let once = false @@ -76,6 +77,7 @@ export function Home() { let prompt: PromptRef const args = useArgs() + const local = useLocal() onMount(() => { if (once) return if (route.initialPrompt) { @@ -84,9 +86,21 @@ export function Home() { } else if (args.prompt) { prompt.set({ input: args.prompt, parts: [] }) once = true - prompt.submit() } }) + + // Wait for sync and model store to be ready before auto-submitting --prompt + createEffect( + on( + () => sync.ready && local.model.ready, + (ready) => { + if (!ready) return + if (!args.prompt) return + if (prompt.current?.input !== args.prompt) return + prompt.submit() + }, + ), + ) const directory = useDirectory() const keybind = useKeybind() From 37ae0a405136a037b5df63d5d19c133de5d5ad95 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Mon, 9 Mar 2026 15:29:55 -0400 Subject: [PATCH 028/200] refactor: replace bun semver with npm semver package --- bun.lock | 296 +++++++++++++++++++++++--- packages/opencode/package.json | 2 + packages/opencode/src/bun/registry.ts | 4 +- packages/script/package.json | 6 +- packages/script/src/index.ts | 3 +- 5 files changed, 274 insertions(+), 37 deletions(-) diff --git a/bun.lock b/bun.lock index bb13d0ea40..3662baec44 100644 --- a/bun.lock +++ b/bun.lock @@ -1086,6 +1086,8 @@ "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], + "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="], "@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="], @@ -1324,7 +1326,9 @@ "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], - "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + "@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="], + + "@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="], "@octokit/auth-app": ["@octokit/auth-app@8.0.1", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.1", "@octokit/auth-oauth-user": "^6.0.0", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg=="], @@ -1998,6 +2002,8 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + "@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="], + "@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="], "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], @@ -2212,6 +2218,8 @@ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], + "ai": ["ai@5.0.124", "", { "dependencies": { "@ai-sdk/gateway": "2.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Li6Jw9F9qsvFJXZPBfxj38ddP2iURCnMs96f9Q3OeQzrDVcl1hvtwSEAuxA/qmfh6SDV2ERqFUOFzigvr0697g=="], "ai-gateway-provider": ["ai-gateway-provider@2.3.1", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.19", "ai": "^5.0.116" }, "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^3.0.71", "@ai-sdk/anthropic": "^2.0.56", "@ai-sdk/azure": "^2.0.90", "@ai-sdk/cerebras": "^1.0.33", "@ai-sdk/cohere": "^2.0.21", "@ai-sdk/deepgram": "^1.0.21", "@ai-sdk/deepseek": "^1.0.32", "@ai-sdk/elevenlabs": "^1.0.21", "@ai-sdk/fireworks": "^1.0.30", "@ai-sdk/google": "^2.0.51", "@ai-sdk/google-vertex": "3.0.90", "@ai-sdk/groq": "^2.0.33", "@ai-sdk/mistral": "^2.0.26", "@ai-sdk/openai": "^2.0.88", "@ai-sdk/perplexity": "^2.0.22", "@ai-sdk/xai": "^2.0.42", "@openrouter/ai-sdk-provider": "^1.5.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^1.0.29" } }, "sha512-PqI6TVNEDNwr7kOhy7XUGnA8XJB1SpeA9aLqGjr0CyWkKgH+y+ofPm8MZGZ74DOwVejDF+POZq0Qs9jKEKUeYg=="], @@ -2244,12 +2252,16 @@ "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="], + "aproba": ["aproba@2.1.0", "", {}, "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew=="], + "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], "arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="], + "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], + "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -2352,6 +2364,8 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + "bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="], "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], @@ -2420,7 +2434,7 @@ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - "cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], + "cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="], "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], @@ -2478,6 +2492,8 @@ "clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="], + "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], @@ -2534,6 +2550,8 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -2600,6 +2618,8 @@ "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], @@ -2620,6 +2640,8 @@ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -2822,6 +2844,8 @@ "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], @@ -2876,6 +2900,8 @@ "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -2918,9 +2944,11 @@ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -2934,6 +2962,8 @@ "fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="], + "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], + "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], @@ -2968,6 +2998,8 @@ "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], @@ -3016,6 +3048,8 @@ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hast-util-embedded": ["hast-util-embedded@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA=="], @@ -3122,6 +3156,8 @@ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="], + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -3192,6 +3228,8 @@ "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -3408,7 +3446,7 @@ "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], - "make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], + "make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="], "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], @@ -3572,9 +3610,9 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], - "minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + "minipass-fetch": ["minipass-fetch@1.4.1", "", { "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "optionalDependencies": { "encoding": "^0.1.12" } }, "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw=="], "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], @@ -3586,6 +3624,8 @@ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], "motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="], @@ -3616,6 +3656,8 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + "native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="], "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], @@ -3630,7 +3672,7 @@ "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], - "node-abi": ["node-abi@4.26.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw=="], + "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], @@ -3642,7 +3684,7 @@ "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - "node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], + "node-gyp": ["node-gyp@8.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w=="], "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], @@ -3660,6 +3702,8 @@ "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="], @@ -3728,7 +3772,7 @@ "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], @@ -3860,6 +3904,8 @@ "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="], @@ -3878,6 +3924,8 @@ "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], "promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="], @@ -3916,6 +3964,8 @@ "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], @@ -4108,6 +4158,8 @@ "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], @@ -4140,6 +4192,10 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], @@ -4164,7 +4220,7 @@ "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], - "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="], "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], @@ -4194,11 +4250,13 @@ "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="], + "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], "srvx": ["srvx@0.9.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ=="], - "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + "ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="], "sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="], @@ -4266,6 +4324,8 @@ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="], "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], @@ -4298,6 +4358,8 @@ "tar": ["tar@7.5.9", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg=="], + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="], @@ -4390,6 +4452,8 @@ "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+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.8.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA=="], @@ -4452,9 +4516,9 @@ "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], - "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], + "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="], - "unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], + "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="], "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], @@ -4604,6 +4668,8 @@ "why-is-node-running": ["why-is-node-running@3.2.2", "", { "bin": { "why-is-node-running": "cli.js" } }, "sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A=="], + "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], "workerd": ["workerd@1.20251118.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251118.0", "@cloudflare/workerd-darwin-arm64": "1.20251118.0", "@cloudflare/workerd-linux-64": "1.20251118.0", "@cloudflare/workerd-linux-arm64": "1.20251118.0", "@cloudflare/workerd-windows-64": "1.20251118.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ=="], @@ -4940,6 +5006,10 @@ "@electron/rebuild/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "@electron/rebuild/node-abi": ["node-abi@4.26.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw=="], + + "@electron/rebuild/node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], + "@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "@electron/universal/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], @@ -5016,6 +5086,12 @@ "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@npmcli/agent/socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "@npmcli/move-file/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], "@octokit/auth-app/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -5212,6 +5288,8 @@ "archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "are-we-there-yet/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], "astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], @@ -5250,9 +5328,19 @@ "c12/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], - "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "cacache/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], - "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "cacache/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "cacache/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "cacache/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "cacache/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "cacache/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], "cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -5334,6 +5422,14 @@ "fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "gauge/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "gauge/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "gauge/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], @@ -5366,7 +5462,13 @@ "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "make-fetch-happen/http-proxy-agent": ["http-proxy-agent@4.0.1", "", { "dependencies": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg=="], + + "make-fetch-happen/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -5382,6 +5484,12 @@ "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-fetch/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -5394,9 +5502,15 @@ "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], - "node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], + "node-gyp/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + "node-gyp/nopt": ["nopt@5.0.0", "", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="], + + "node-gyp/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "node-gyp/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "node-gyp/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], @@ -5454,6 +5568,8 @@ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "prebuild-install/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -5494,6 +5610,12 @@ "sitemap/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "sqlite3/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], "sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="], @@ -5514,6 +5636,10 @@ "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], @@ -5556,6 +5682,8 @@ "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "wide-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -5688,6 +5816,12 @@ "@electron/notarize/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "@electron/rebuild/node-gyp/make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], + + "@electron/rebuild/node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], + + "@electron/rebuild/node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + "@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -5782,6 +5916,8 @@ "@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + "@npmcli/move-file/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], "@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], @@ -5956,11 +6092,11 @@ "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "cacache/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "cacache/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], - "cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "cacache/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], "cli-truncate/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -5994,6 +6130,10 @@ "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "gauge/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "gauge/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -6008,11 +6148,25 @@ "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "make-fetch-happen/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "make-fetch-happen/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "motion/framer-motion/motion-dom": ["motion-dom@12.34.5", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-k33CsnxO2K3gBRMUZT+vPmc4Utlb5menKdG0RyVNLtlqRaaJPRWlE9fXl8NTtfZ5z3G8TDvqSu0MENLqSTaHZA=="], - "node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + "node-gyp/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "node-gyp/nopt/abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], + + "node-gyp/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "node-gyp/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "node-gyp/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "node-gyp/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "node-gyp/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], @@ -6050,6 +6204,14 @@ "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "sqlite3/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "sqlite3/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "sqlite3/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "sqlite3/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], "storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], @@ -6106,6 +6268,10 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "tar-fs/tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], @@ -6176,6 +6342,10 @@ "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "wide-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wide-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], @@ -6290,6 +6460,18 @@ "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@electron/rebuild/node-gyp/make-fetch-happen/cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + + "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + + "@electron/rebuild/node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -6352,6 +6534,8 @@ "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "@npmcli/move-file/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], "@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -6400,9 +6584,9 @@ "babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - "cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "cacache/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6430,6 +6614,10 @@ "js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "node-gyp/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], @@ -6466,12 +6654,18 @@ "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "sqlite3/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "tar-fs/tar-stream/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "wide-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@astrojs/check/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@astrojs/check/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6504,12 +6698,28 @@ "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], + "@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@electron/rebuild/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], @@ -6522,10 +6732,6 @@ "babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "electron-builder/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6538,6 +6744,8 @@ "js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "node-gyp/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], @@ -6550,20 +6758,28 @@ "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "tar-fs/tar-stream/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/unique-filename/unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], + + "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -6573,5 +6789,19 @@ "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], } } diff --git a/packages/opencode/package.json b/packages/opencode/package.json index c560315d33..4e4f46b0c6 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -41,6 +41,7 @@ "@types/babel__core": "7.20.5", "@types/bun": "catalog:", "@types/mime-types": "3.0.1", + "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", "@types/yargs": "17.0.33", "@types/which": "3.0.4", @@ -121,6 +122,7 @@ "opentui-spinner": "0.0.6", "partial-json": "0.1.7", "remeda": "catalog:", + "semver": "^7.6.3", "solid-js": "catalog:", "strip-ansi": "7.1.2", "tree-sitter-bash": "0.25.0", diff --git a/packages/opencode/src/bun/registry.ts b/packages/opencode/src/bun/registry.ts index a85a6c989c..1fc8531442 100644 --- a/packages/opencode/src/bun/registry.ts +++ b/packages/opencode/src/bun/registry.ts @@ -1,4 +1,4 @@ -import { semver } from "bun" +import semver from "semver" import { text } from "node:stream/consumers" import { Log } from "../util/log" import { Process } from "../util/process" @@ -45,6 +45,6 @@ export namespace PackageRegistry { const isRange = /[\s^~*xX<>|=]/.test(cachedVersion) if (isRange) return !semver.satisfies(latestVersion, cachedVersion) - return semver.order(cachedVersion, latestVersion) === -1 + return semver.lt(cachedVersion, latestVersion) } } diff --git a/packages/script/package.json b/packages/script/package.json index 45de3bcb99..aa0d2f563f 100644 --- a/packages/script/package.json +++ b/packages/script/package.json @@ -2,8 +2,12 @@ "$schema": "https://json.schemastore.org/package", "name": "@opencode-ai/script", "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, "devDependencies": { - "@types/bun": "catalog:" + "@types/bun": "catalog:", + "@types/semver": "^7.5.8" }, "exports": { ".": "./src/index.ts" diff --git a/packages/script/src/index.ts b/packages/script/src/index.ts index ee4bc1e465..d148ce0d2b 100644 --- a/packages/script/src/index.ts +++ b/packages/script/src/index.ts @@ -1,4 +1,5 @@ -import { $, semver } from "bun" +import { $ } from "bun" +import semver from "semver" import path from "path" const rootPkgPath = path.resolve(import.meta.dir, "../../../package.json") From 2518fd81f6ec638b3b0a896466d0eb29a1d6cd70 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:31:33 +0000 Subject: [PATCH 029/200] chore: generate --- bun.lock | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bun.lock b/bun.lock index 3662baec44..4b7097232a 100644 --- a/bun.lock +++ b/bun.lock @@ -366,6 +366,7 @@ "opentui-spinner": "0.0.6", "partial-json": "0.1.7", "remeda": "catalog:", + "semver": "^7.6.3", "solid-js": "catalog:", "strip-ansi": "7.1.2", "tree-sitter-bash": "0.25.0", @@ -395,6 +396,7 @@ "@types/babel__core": "7.20.5", "@types/bun": "catalog:", "@types/mime-types": "3.0.1", + "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", "@types/which": "3.0.4", "@types/yargs": "17.0.33", @@ -423,8 +425,12 @@ }, "packages/script": { "name": "@opencode-ai/script", + "dependencies": { + "semver": "^7.6.3", + }, "devDependencies": { "@types/bun": "catalog:", + "@types/semver": "^7.5.8", }, }, "packages/sdk/js": { @@ -2110,6 +2116,8 @@ "@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="], + "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="], + "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], From 3627c67cf25fdfa4e36d2b49b83e3f93c069c286 Mon Sep 17 00:00:00 2001 From: bhaktatejas922 <bhaktatejas922@gmail.com> Date: Mon, 9 Mar 2026 12:39:06 -0700 Subject: [PATCH 030/200] docs: update opencode-morph-fast-apply to opencode-morph-plugin in ecosystem (#16634) --- packages/web/src/content/docs/ecosystem.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/ecosystem.mdx b/packages/web/src/content/docs/ecosystem.mdx index 9522e9cf8b..d5a1e853df 100644 --- a/packages/web/src/content/docs/ecosystem.mdx +++ b/packages/web/src/content/docs/ecosystem.mdx @@ -32,7 +32,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply editing, WarpGrep codebase search, and context compaction via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events | From 7e10c728d4d3c137b44b88fc42e0d192df623f06 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:49:01 +0000 Subject: [PATCH 031/200] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 73491735f4..1fe3266c35 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-4kjoJ06VNvHltPHfzQRBG0bC6R39jao10ffGzrNZ230=", - "aarch64-linux": "sha256-6Uio+S2rcyBWbBEeOZb9N1CCKgkbKi68lOIKi3Ws/pQ=", - "aarch64-darwin": "sha256-8ngN5KVN4vhdsk0QJ11BGgSVBrcaEbwSj23c77HBpgs=", - "x86_64-darwin": "sha256-v/ueYGb9a0Nymzy+mkO4uQr78DAuJnES1qOT0onFgnQ=" + "x86_64-linux": "sha256-TnrYykX8Mf/Ugtkix6V", + "aarch64-linux": "sha256-TnrYykX8Mf/Ugtkix6V", + "aarch64-darwin": "sha256-TnrYykX8Mf/Ugtkix6V", + "x86_64-darwin": "sha256-TnrYykX8Mf/Ugtkix6V" } } From ce38997c76c3ebdd0d6b10ec38cb0aa2a57532dd Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:51:58 +0000 Subject: [PATCH 032/200] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 1fe3266c35..2190feb256 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-TnrYykX8Mf/Ugtkix6V", - "aarch64-linux": "sha256-TnrYykX8Mf/Ugtkix6V", - "aarch64-darwin": "sha256-TnrYykX8Mf/Ugtkix6V", - "x86_64-darwin": "sha256-TnrYykX8Mf/Ugtkix6V" + "x86_64-linux": "sha256-+SMpaj0jeIHjlddAu6QIwojmWFVIiA8/G32hiQMjcOk=", + "aarch64-linux": "sha256-uo63IF6OCMab+O3ngn1sVxqIGJMm04HXuDgIRmXNTNk=", + "aarch64-darwin": "sha256-yB2tWm6AsX6UifnDqe7VldhN5zTQkDoqZ87AGQYjxT4=", + "x86_64-darwin": "sha256-nNhtqMSG4/y+uxjj14Jc5QQ7X6hQli9ni4v56XAvaAU=" } } From 86cef169404cd52a57c2ce0cec344b0aca5401c3 Mon Sep 17 00:00:00 2001 From: James Long <longster@gmail.com> Date: Mon, 9 Mar 2026 16:14:19 -0400 Subject: [PATCH 033/200] fix(core): put workspace routing behind OPENCODE_EXPERIMENTAL_WORKSPACES flag (#16775) --- packages/opencode/src/cli/cmd/tui/app.tsx | 2 +- .../opencode/src/cli/cmd/tui/routes/session/header.tsx | 4 ++-- .../src/control-plane/workspace-router-middleware.ts | 5 ++--- packages/opencode/src/flag/flag.ts | 4 ++-- .../control-plane/session-proxy-middleware.test.ts | 10 ++++++++++ 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index e939b831d5..d5aef34f6e 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -372,7 +372,7 @@ function App() { dialog.replace(() => <DialogSessionList />) }, }, - ...(Flag.OPENCODE_EXPERIMENTAL_WORKSPACES_TUI + ...(Flag.OPENCODE_EXPERIMENTAL_WORKSPACES ? [ { title: "Manage workspaces", diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx index 49b2d61091..f64dbe533a 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/header.tsx @@ -103,7 +103,7 @@ export function Header() { <Match when={session()?.parentID}> <box flexDirection="column" gap={1}> <box flexDirection={narrow() ? "column" : "row"} justifyContent="space-between" gap={narrow() ? 1 : 0}> - {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES_TUI ? ( + {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES ? ( <box flexDirection="column"> <text fg={theme.text}> <b>Subagent session</b> @@ -154,7 +154,7 @@ export function Header() { </Match> <Match when={true}> <box flexDirection={narrow() ? "column" : "row"} justifyContent="space-between" gap={1}> - {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES_TUI ? ( + {Flag.OPENCODE_EXPERIMENTAL_WORKSPACES ? ( <box flexDirection="column"> <Title session={session} /> <WorkspaceInfo workspace={workspace} /> diff --git a/packages/opencode/src/control-plane/workspace-router-middleware.ts b/packages/opencode/src/control-plane/workspace-router-middleware.ts index b48f2fd2b7..463a95ef2b 100644 --- a/packages/opencode/src/control-plane/workspace-router-middleware.ts +++ b/packages/opencode/src/control-plane/workspace-router-middleware.ts @@ -1,6 +1,5 @@ -import { Instance } from "@/project/instance" import type { MiddlewareHandler } from "hono" -import { Installation } from "../installation" +import { Flag } from "../flag/flag" import { getAdaptor } from "./adaptors" import { Workspace } from "./workspace" import { WorkspaceContext } from "./workspace-context" @@ -38,7 +37,7 @@ async function routeRequest(req: Request) { export const WorkspaceRouterMiddleware: MiddlewareHandler = async (c, next) => { // Only available in development for now - if (!Installation.isLocal()) { + if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) { return next() } diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 7c48e99f47..2488e76758 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -57,8 +57,8 @@ export namespace Flag { export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL") export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK") export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE") - export const OPENCODE_EXPERIMENTAL_WORKSPACES_TUI = - OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES_TUI") + export const OPENCODE_EXPERIMENTAL_WORKSPACES = + OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES") export const OPENCODE_EXPERIMENTAL_MARKDOWN = !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN") export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"] export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"] diff --git a/packages/opencode/test/control-plane/session-proxy-middleware.test.ts b/packages/opencode/test/control-plane/session-proxy-middleware.test.ts index 369b9152ae..c674d95ec7 100644 --- a/packages/opencode/test/control-plane/session-proxy-middleware.test.ts +++ b/packages/opencode/test/control-plane/session-proxy-middleware.test.ts @@ -10,12 +10,22 @@ import { Database } from "../../src/storage/db" import { resetDatabase } from "../fixture/db" import * as adaptors from "../../src/control-plane/adaptors" import type { Adaptor } from "../../src/control-plane/types" +import { Flag } from "../../src/flag/flag" afterEach(async () => { mock.restore() await resetDatabase() }) +const original = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES +// @ts-expect-error don't do this normally, but it works +Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true + +afterEach(() => { + // @ts-expect-error don't do this normally, but it works + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = original +}) + type State = { workspace?: "first" | "second" calls: Array<{ method: string; url: string; body?: string }> From d9a97249c0a217999120d6f032e636ebd87d9a59 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:15:31 +0000 Subject: [PATCH 034/200] chore: generate --- packages/opencode/src/flag/flag.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 2488e76758..30929bd926 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -57,8 +57,7 @@ export namespace Flag { export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL") export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK") export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE") - export const OPENCODE_EXPERIMENTAL_WORKSPACES = - OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES") + export const OPENCODE_EXPERIMENTAL_WORKSPACES = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES") export const OPENCODE_EXPERIMENTAL_MARKDOWN = !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN") export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"] export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"] From e53aa79dc6836c0d04eecf67f3c08f7b41a3ebf7 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:17:28 -0500 Subject: [PATCH 035/200] chore: cleanup --- packages/ui/src/components/message-part.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 8fc7090133..80da436135 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -168,7 +168,7 @@ align-items: center; justify-content: flex-end; overflow: hidden; - gap: 6px; + gap: 0; } [data-slot="user-message-meta-tail"] { From 7b773c65ecc5b9e4796aa320723645d6e7431474 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:19:56 -0500 Subject: [PATCH 036/200] chore: cleanup --- packages/ui/src/components/message-part.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 80da436135..704a57e5b0 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -21,7 +21,7 @@ align-self: stretch; width: 100%; max-width: 100%; - gap: 8px; + gap: 0; &[data-interrupted] { color: var(--text-weak); @@ -98,6 +98,10 @@ align-items: flex-end; } + [data-slot="user-message-attachments"] + [data-slot="user-message-body"] { + margin-top: 8px; + } + [data-slot="user-message-text"] { display: inline-block; white-space: pre-wrap; From 794532928f76c197181bc80944e8be1f6e5eda9a Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:28:29 -0500 Subject: [PATCH 037/200] fix(app): terminal state corruption --- packages/app/src/context/terminal.test.ts | 43 ++++++++++ packages/app/src/context/terminal.tsx | 98 ++++++++++++++++------- 2 files changed, 112 insertions(+), 29 deletions(-) diff --git a/packages/app/src/context/terminal.test.ts b/packages/app/src/context/terminal.test.ts index a250de57c0..6e07e03124 100644 --- a/packages/app/src/context/terminal.test.ts +++ b/packages/app/src/context/terminal.test.ts @@ -2,6 +2,7 @@ import { beforeAll, describe, expect, mock, test } from "bun:test" let getWorkspaceTerminalCacheKey: (dir: string) => string let getLegacyTerminalStorageKeys: (dir: string, legacySessionID?: string) => string[] +let migrateTerminalState: (value: unknown) => unknown beforeAll(async () => { mock.module("@solidjs/router", () => ({ @@ -17,6 +18,7 @@ beforeAll(async () => { const mod = await import("./terminal") getWorkspaceTerminalCacheKey = mod.getWorkspaceTerminalCacheKey getLegacyTerminalStorageKeys = mod.getLegacyTerminalStorageKeys + migrateTerminalState = mod.migrateTerminalState }) describe("getWorkspaceTerminalCacheKey", () => { @@ -37,3 +39,44 @@ describe("getLegacyTerminalStorageKeys", () => { ]) }) }) + +describe("migrateTerminalState", () => { + test("drops invalid terminals and restores a valid active terminal", () => { + expect( + migrateTerminalState({ + active: "missing", + all: [ + null, + { id: "one", title: "Terminal 2" }, + { id: "one", title: "duplicate", titleNumber: 9 }, + { id: "two", title: "logs", titleNumber: 4, rows: 24, cols: 80 }, + { title: "no-id" }, + ], + }), + ).toEqual({ + active: "one", + all: [ + { id: "one", title: "Terminal 2", titleNumber: 2 }, + { id: "two", title: "logs", titleNumber: 4, rows: 24, cols: 80 }, + ], + }) + }) + + test("keeps a valid active id", () => { + expect( + migrateTerminalState({ + active: "two", + all: [ + { id: "one", title: "Terminal 1" }, + { id: "two", title: "shell", titleNumber: 7 }, + ], + }), + ).toEqual({ + active: "two", + all: [ + { id: "one", title: "Terminal 1", titleNumber: 1 }, + { id: "two", title: "shell", titleNumber: 7 }, + ], + }) + }) +}) diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx index 4467495b79..a2807375ff 100644 --- a/packages/app/src/context/terminal.tsx +++ b/packages/app/src/context/terminal.tsx @@ -20,6 +20,71 @@ export type LocalPTY = { const WORKSPACE_KEY = "__workspace__" const MAX_TERMINAL_SESSIONS = 20 +function record(value: unknown): value is Record<string, unknown> { + return typeof value === "object" && value !== null && !Array.isArray(value) +} + +function text(value: unknown) { + return typeof value === "string" ? value : undefined +} + +function num(value: unknown) { + return typeof value === "number" && Number.isFinite(value) ? value : undefined +} + +function numberFromTitle(title: string) { + const match = title.match(/^Terminal (\d+)$/) + if (!match) return + const value = Number(match[1]) + if (!Number.isFinite(value) || value <= 0) return + return value +} + +function pty(value: unknown): LocalPTY | undefined { + if (!record(value)) return + + const id = text(value.id) + if (!id) return + + const title = text(value.title) ?? "" + const number = num(value.titleNumber) + const rows = num(value.rows) + const cols = num(value.cols) + const buffer = text(value.buffer) + const scrollY = num(value.scrollY) + const cursor = num(value.cursor) + + return { + id, + title, + titleNumber: number && number > 0 ? number : (numberFromTitle(title) ?? 0), + ...(rows !== undefined ? { rows } : {}), + ...(cols !== undefined ? { cols } : {}), + ...(buffer !== undefined ? { buffer } : {}), + ...(scrollY !== undefined ? { scrollY } : {}), + ...(cursor !== undefined ? { cursor } : {}), + } +} + +export function migrateTerminalState(value: unknown) { + if (!record(value)) return value + + const seen = new Set<string>() + const all = (Array.isArray(value.all) ? value.all : []).flatMap((item) => { + const next = pty(item) + if (!next || seen.has(next.id)) return [] + seen.add(next.id) + return [next] + }) + + const active = text(value.active) + + return { + active: active && seen.has(active) ? active : all[0]?.id, + all, + } +} + export function getWorkspaceTerminalCacheKey(dir: string) { return `${dir}:${WORKSPACE_KEY}` } @@ -71,16 +136,11 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: string, legacySessionID?: string) { const legacy = getLegacyTerminalStorageKeys(dir, legacySessionID) - const numberFromTitle = (title: string) => { - const match = title.match(/^Terminal (\d+)$/) - if (!match) return - const value = Number(match[1]) - if (!Number.isFinite(value) || value <= 0) return - return value - } - const [store, setStore, _, ready] = persisted( - Persist.workspace(dir, "terminal", legacy), + { + ...Persist.workspace(dir, "terminal", legacy), + migrate: migrateTerminalState, + }, createStore<{ active?: string all: LocalPTY[] @@ -128,26 +188,6 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str }) onCleanup(unsub) - const meta = { migrated: false } - - createEffect(() => { - if (!ready()) return - if (meta.migrated) return - meta.migrated = true - - setStore("all", (all) => { - const next = all.map((pty) => { - const direct = Number.isFinite(pty.titleNumber) && pty.titleNumber > 0 ? pty.titleNumber : undefined - if (direct !== undefined) return pty - const parsed = numberFromTitle(pty.title) - if (parsed === undefined) return pty - return { ...pty, titleNumber: parsed } - }) - if (next.every((pty, index) => pty === all[index])) return all - return next - }) - }) - return { ready, all: createMemo(() => store.all), From ee18c9976e00ebf0162fe1dcc47b209e0507b3e5 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:57:19 -0500 Subject: [PATCH 038/200] chore(app): dev stats --- packages/app/src/components/debug-bar.tsx | 432 ++++++++++++++++++++++ packages/app/src/pages/layout.tsx | 368 +++++++++--------- 2 files changed, 622 insertions(+), 178 deletions(-) create mode 100644 packages/app/src/components/debug-bar.tsx diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx new file mode 100644 index 0000000000..93a4654f86 --- /dev/null +++ b/packages/app/src/components/debug-bar.tsx @@ -0,0 +1,432 @@ +import { useIsRouting, useLocation } from "@solidjs/router" +import { batch, createEffect, onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" +import { Tooltip } from "@opencode-ai/ui/tooltip" + +type Mem = Performance & { + memory?: { + usedJSHeapSize: number + jsHeapSizeLimit: number + } +} + +type Evt = PerformanceEntry & { + interactionId?: number + processingStart?: number +} + +type Shift = PerformanceEntry & { + hadRecentInput: boolean + value: number +} + +type Obs = PerformanceObserverInit & { + durationThreshold?: number +} + +const span = 5000 + +const ms = (n?: number, d = 0) => { + if (n === undefined || Number.isNaN(n)) return "n/a" + return `${n.toFixed(d)}ms` +} + +const time = (n?: number) => { + if (n === undefined || Number.isNaN(n)) return "n/a" + return `${Math.round(n)}` +} + +const mb = (n?: number) => { + if (n === undefined || Number.isNaN(n)) return "n/a" + const v = n / 1024 / 1024 + return `${v >= 1024 ? v.toFixed(0) : v.toFixed(1)}MB` +} + +const bad = (n: number | undefined, limit: number, low = false) => { + if (n === undefined || Number.isNaN(n)) return false + return low ? n < limit : n > limit +} + +const session = (path: string) => path.includes("/session") + +function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string }) { + return ( + <Tooltip value={props.tip} placement="left"> + <div class="flex w-full flex-col items-center px-0.5 py-1 text-center"> + <div class="text-[7px] font-black uppercase tracking-[0.04em] opacity-70 leading-none">{props.label}</div> + <div + classList={{ + "text-[9px] font-semibold leading-none tabular-nums": true, + "text-text-on-critical-base": !!props.bad, + "opacity-70": !!props.dim, + }} + > + {props.value} + </div> + </div> + </Tooltip> + ) +} + +export function DebugBar() { + const location = useLocation() + const routing = useIsRouting() + const [state, setState] = createStore({ + cls: undefined as number | undefined, + delay: undefined as number | undefined, + fps: undefined as number | undefined, + gap: undefined as number | undefined, + heap: { + limit: undefined as number | undefined, + used: undefined as number | undefined, + }, + inp: undefined as number | undefined, + jank: undefined as number | undefined, + long: { + block: undefined as number | undefined, + count: undefined as number | undefined, + max: undefined as number | undefined, + }, + nav: { + dur: undefined as number | undefined, + pending: false, + }, + }) + + const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined) + const heapv = () => { + const value = heap() + if (value === undefined) return "n/a" + return `${Math.round(value * 100)}%` + } + const longv = () => (state.long.count === undefined ? "n/a" : `${time(state.long.block)}/${state.long.count}`) + const navv = () => (state.nav.pending ? "..." : time(state.nav.dur)) + + let prev = "" + let start = 0 + let init = false + let one = 0 + let two = 0 + + createEffect(() => { + const busy = routing() + const next = `${location.pathname}${location.search}` + + if (!init) { + init = true + prev = next + return + } + + if (busy) { + if (one !== 0) cancelAnimationFrame(one) + if (two !== 0) cancelAnimationFrame(two) + one = 0 + two = 0 + if (start !== 0) return + start = performance.now() + if (session(prev)) setState("nav", { dur: undefined, pending: true }) + return + } + + if (start === 0) { + prev = next + return + } + + const at = start + const from = prev + start = 0 + prev = next + + if (!(session(from) || session(next))) return + + if (one !== 0) cancelAnimationFrame(one) + if (two !== 0) cancelAnimationFrame(two) + one = requestAnimationFrame(() => { + one = 0 + two = requestAnimationFrame(() => { + two = 0 + setState("nav", { dur: performance.now() - at, pending: false }) + }) + }) + }) + + onMount(() => { + const obs: PerformanceObserver[] = [] + const fps: Array<{ at: number; dur: number }> = [] + const long: Array<{ at: number; dur: number }> = [] + const seen = new Map<number | string, { at: number; delay: number; dur: number }>() + let hasLong = false + let poll: number | undefined + let raf = 0 + let last = 0 + let snap = 0 + + const trim = (list: Array<{ at: number; dur: number }>, span: number, at: number) => { + while (list[0] && at - list[0].at > span) list.shift() + } + + const syncFrame = (at: number) => { + trim(fps, span, at) + const total = fps.reduce((sum, entry) => sum + entry.dur, 0) + const gap = fps.reduce((max, entry) => Math.max(max, entry.dur), 0) + const jank = fps.filter((entry) => entry.dur > 32).length + batch(() => { + setState("fps", total > 0 ? (fps.length * 1000) / total : undefined) + setState("gap", gap > 0 ? gap : undefined) + setState("jank", jank) + }) + } + + const syncLong = (at = performance.now()) => { + if (!hasLong) return + trim(long, span, at) + const block = long.reduce((sum, entry) => sum + Math.max(0, entry.dur - 50), 0) + const max = long.reduce((hi, entry) => Math.max(hi, entry.dur), 0) + setState("long", { block, count: long.length, max }) + } + + const syncInp = (at = performance.now()) => { + for (const [key, entry] of seen) { + if (at - entry.at > span) seen.delete(key) + } + let delay = 0 + let inp = 0 + for (const entry of seen.values()) { + delay = Math.max(delay, entry.delay) + inp = Math.max(inp, entry.dur) + } + batch(() => { + setState("delay", delay > 0 ? delay : undefined) + setState("inp", inp > 0 ? inp : undefined) + }) + } + + const syncHeap = () => { + const mem = (performance as Mem).memory + if (!mem) return + setState("heap", { limit: mem.jsHeapSizeLimit, used: mem.usedJSHeapSize }) + } + + const reset = () => { + fps.length = 0 + long.length = 0 + seen.clear() + last = 0 + snap = 0 + batch(() => { + setState("fps", undefined) + setState("gap", undefined) + setState("jank", undefined) + setState("delay", undefined) + setState("inp", undefined) + if (hasLong) setState("long", { block: 0, count: 0, max: 0 }) + }) + } + + const watch = (type: string, init: Obs, fn: (entries: PerformanceEntry[]) => void) => { + if (typeof PerformanceObserver === "undefined") return false + if (!(PerformanceObserver.supportedEntryTypes ?? []).includes(type)) return false + const ob = new PerformanceObserver((list) => fn(list.getEntries())) + try { + ob.observe(init) + obs.push(ob) + return true + } catch { + ob.disconnect() + return false + } + } + + if ( + watch("layout-shift", { buffered: true, type: "layout-shift" }, (entries) => { + const add = entries.reduce((sum, entry) => { + const item = entry as Shift + if (item.hadRecentInput) return sum + return sum + item.value + }, 0) + if (add === 0) return + setState("cls", (value) => (value ?? 0) + add) + }) + ) { + setState("cls", 0) + } + + if ( + watch("longtask", { buffered: true, type: "longtask" }, (entries) => { + const at = performance.now() + long.push(...entries.map((entry) => ({ at: entry.startTime, dur: entry.duration }))) + syncLong(at) + }) + ) { + hasLong = true + setState("long", { block: 0, count: 0, max: 0 }) + } + + watch("event", { buffered: true, durationThreshold: 16, type: "event" }, (entries) => { + for (const raw of entries) { + const entry = raw as Evt + if (entry.duration < 16) continue + const key = + entry.interactionId && entry.interactionId > 0 + ? entry.interactionId + : `${entry.name}:${Math.round(entry.startTime)}` + const prev = seen.get(key) + const delay = Math.max(0, (entry.processingStart ?? entry.startTime) - entry.startTime) + seen.set(key, { + at: entry.startTime, + delay: Math.max(prev?.delay ?? 0, delay), + dur: Math.max(prev?.dur ?? 0, entry.duration), + }) + if (seen.size <= 200) continue + const first = seen.keys().next().value + if (first !== undefined) seen.delete(first) + } + syncInp() + }) + + const loop = (at: number) => { + if (document.visibilityState !== "visible") { + raf = 0 + return + } + + if (last === 0) { + last = at + raf = requestAnimationFrame(loop) + return + } + + fps.push({ at, dur: at - last }) + last = at + + if (at - snap >= 250) { + snap = at + syncFrame(at) + } + + raf = requestAnimationFrame(loop) + } + + const stop = () => { + if (raf !== 0) cancelAnimationFrame(raf) + raf = 0 + if (poll === undefined) return + clearInterval(poll) + poll = undefined + } + + const start = () => { + if (document.visibilityState !== "visible") return + if (poll === undefined) { + poll = window.setInterval(() => { + syncLong() + syncInp() + syncHeap() + }, 1000) + } + if (raf !== 0) return + raf = requestAnimationFrame(loop) + } + + const vis = () => { + if (document.visibilityState !== "visible") { + stop() + return + } + reset() + start() + } + + syncHeap() + start() + document.addEventListener("visibilitychange", vis) + + onCleanup(() => { + if (one !== 0) cancelAnimationFrame(one) + if (two !== 0) cancelAnimationFrame(two) + stop() + document.removeEventListener("visibilitychange", vis) + for (const ob of obs) ob.disconnect() + }) + }) + + return ( + <aside + aria-label="Development performance diagnostics" + class="pointer-events-auto h-full min-h-0 w-[36px] shrink-0 overflow-y-auto text-text-on-interactive-base no-scrollbar sm:w-[38px]" + style={{ "background-color": "color-mix(in srgb, var(--icon-interactive-base) 42%, black)" }} + > + <div class="flex min-h-full flex-col gap-0.5 py-2 font-mono"> + <Cell + label="NAV" + tip="Last completed route transition touching a session page, measured from router start until the first paint after it settles." + value={navv()} + bad={bad(state.nav.dur, 400)} + dim={state.nav.dur === undefined && !state.nav.pending} + /> + <Cell + label="FPS" + tip="Rolling frames per second over the last 5 seconds." + value={state.fps === undefined ? "n/a" : `${Math.round(state.fps)}`} + bad={bad(state.fps, 50, true)} + dim={state.fps === undefined} + /> + <Cell + label="FRM" + tip="Worst frame time over the last 5 seconds." + value={time(state.gap)} + bad={bad(state.gap, 50)} + dim={state.gap === undefined} + /> + <Cell + label="JNK" + tip="Frames over 32ms in the last 5 seconds." + value={state.jank === undefined ? "n/a" : `${state.jank}`} + bad={bad(state.jank, 8)} + dim={state.jank === undefined} + /> + <Cell + label="LNG" + tip={`Blocked time and long-task count in the last 5 seconds. Max task: ${ms(state.long.max)}.`} + value={longv()} + bad={bad(state.long.block, 200)} + dim={state.long.count === undefined} + /> + <Cell + label="DLY" + tip="Worst observed input delay in the last 5 seconds." + value={time(state.delay)} + bad={bad(state.delay, 100)} + dim={state.delay === undefined} + /> + <Cell + label="INP" + tip="Approximate interaction duration over the last 5 seconds. This is INP-like, not the official Web Vitals INP." + value={time(state.inp)} + bad={bad(state.inp, 200)} + dim={state.inp === undefined} + /> + <Cell + label="CLS" + tip="Cumulative layout shift for the current app lifetime." + value={state.cls === undefined ? "n/a" : state.cls.toFixed(2)} + bad={bad(state.cls, 0.1)} + dim={state.cls === undefined} + /> + <Cell + label="MEM" + tip={ + state.heap.used === undefined + ? "Used JS heap vs heap limit. Chromium only." + : `Used JS heap vs heap limit. ${mb(state.heap.used)} of ${mb(state.heap.limit)}.` + } + value={heapv()} + bad={bad(heap(), 0.8)} + dim={state.heap.used === undefined} + /> + </div> + </aside> + ) +} diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 40015db1be..052a03c549 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -54,6 +54,7 @@ import { useCommand, type CommandOption } from "@/context/command" import { ConstrainDragXAxis } from "@/utils/solid-dnd" import { DialogSelectDirectory } from "@/components/dialog-select-directory" import { DialogEditProject } from "@/components/dialog-edit-project" +import { DebugBar } from "@/components/debug-bar" import { Titlebar } from "@/components/titlebar" import { useServer } from "@/context/server" import { useLanguage, type Locale } from "@/context/language" @@ -2135,193 +2136,204 @@ export default function Layout(props: ParentProps) { } return ( - <div class="relative bg-background-base flex-1 min-h-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text"> + <div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text"> <Titlebar /> - <div class="flex-1 min-h-0 relative overflow-x-hidden"> - <nav - aria-label={language.t("sidebar.nav.projectsAndSessions")} - data-component="sidebar-nav-desktop" - classList={{ - "hidden xl:block": true, - "absolute inset-y-0 left-0": true, - "z-10": true, - }} - style={{ width: `${Math.max(layout.sidebar.width(), 244)}px` }} - ref={(el) => { - setState("nav", el) - }} - onMouseEnter={() => { - disarm() - }} - onMouseLeave={() => { - aim.reset() - if (!sidebarHovering()) return + <div class="flex-1 min-h-0 min-w-0 flex"> + <div class="flex-1 min-h-0 relative"> + <div class="size-full relative overflow-x-hidden"> + <nav + aria-label={language.t("sidebar.nav.projectsAndSessions")} + data-component="sidebar-nav-desktop" + classList={{ + "hidden xl:block": true, + "absolute inset-y-0 left-0": true, + "z-10": true, + }} + style={{ width: `${Math.max(layout.sidebar.width(), 244)}px` }} + ref={(el) => { + setState("nav", el) + }} + onMouseEnter={() => { + disarm() + }} + onMouseLeave={() => { + aim.reset() + if (!sidebarHovering()) return - arm() - }} - > - <div class="@container w-full h-full contain-strict"> - <SidebarContent - opened={() => layout.sidebar.opened()} - aimMove={aim.move} - projects={() => layout.projects.list()} - renderProject={(project) => ( - <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} /> - )} - handleDragStart={handleDragStart} - handleDragEnd={handleDragEnd} - handleDragOver={handleDragOver} - openProjectLabel={language.t("command.project.open")} - openProjectKeybind={() => command.keybind("project.open")} - onOpenProject={chooseProject} - renderProjectOverlay={() => ( - <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} /> - )} - settingsLabel={() => language.t("sidebar.settings")} - settingsKeybind={() => command.keybind("settings.open")} - onOpenSettings={openSettings} - helpLabel={() => language.t("sidebar.help")} - onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")} - renderPanel={() => ( - <Show when={currentProject()} keyed> - {(project) => <SidebarPanel project={project} merged />} - </Show> - )} + arm() + }} + > + <div class="@container w-full h-full contain-strict"> + <SidebarContent + opened={() => layout.sidebar.opened()} + aimMove={aim.move} + projects={() => layout.projects.list()} + renderProject={(project) => ( + <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} /> + )} + handleDragStart={handleDragStart} + handleDragEnd={handleDragEnd} + handleDragOver={handleDragOver} + openProjectLabel={language.t("command.project.open")} + openProjectKeybind={() => command.keybind("project.open")} + onOpenProject={chooseProject} + renderProjectOverlay={() => ( + <ProjectDragOverlay + projects={() => layout.projects.list()} + activeProject={() => store.activeProject} + /> + )} + settingsLabel={() => language.t("sidebar.settings")} + settingsKeybind={() => command.keybind("settings.open")} + onOpenSettings={openSettings} + helpLabel={() => language.t("sidebar.help")} + onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")} + renderPanel={() => ( + <Show when={currentProject()} keyed> + {(project) => <SidebarPanel project={project} merged />} + </Show> + )} + /> + </div> + <Show when={layout.sidebar.opened()}> + <div onPointerDown={() => setSizing(true)}> + <ResizeHandle + direction="horizontal" + size={layout.sidebar.width()} + min={244} + max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64} + collapseThreshold={244} + onResize={(w) => { + setSizing(true) + if (sizet !== undefined) clearTimeout(sizet) + sizet = window.setTimeout(() => setSizing(false), 120) + layout.sidebar.resize(w) + }} + onCollapse={layout.sidebar.close} + /> + </div> + </Show> + </nav> + + <div + class="hidden xl:block pointer-events-none absolute top-0 right-0 z-0 border-t border-border-weaker-base" + style={{ left: "calc(4rem + 12px)" }} /> - </div> - <Show when={layout.sidebar.opened()}> - <div onPointerDown={() => setSizing(true)}> - <ResizeHandle - direction="horizontal" - size={layout.sidebar.width()} - min={244} - max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64} - collapseThreshold={244} - onResize={(w) => { - setSizing(true) - if (sizet !== undefined) clearTimeout(sizet) - sizet = window.setTimeout(() => setSizing(false), 120) - layout.sidebar.resize(w) + + <div class="xl:hidden"> + <div + classList={{ + "fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true, + "opacity-100 pointer-events-auto": layout.mobileSidebar.opened(), + "opacity-0 pointer-events-none": !layout.mobileSidebar.opened(), + }} + onClick={(e) => { + if (e.target === e.currentTarget) layout.mobileSidebar.hide() }} - onCollapse={layout.sidebar.close} /> + <nav + aria-label={language.t("sidebar.nav.projectsAndSessions")} + data-component="sidebar-nav-mobile" + classList={{ + "@container fixed top-10 bottom-0 left-0 z-50 w-full max-w-[400px] overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true, + "translate-x-0": layout.mobileSidebar.opened(), + "-translate-x-full": !layout.mobileSidebar.opened(), + }} + onClick={(e) => e.stopPropagation()} + > + <SidebarContent + mobile + opened={() => layout.sidebar.opened()} + aimMove={aim.move} + projects={() => layout.projects.list()} + renderProject={(project) => ( + <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile /> + )} + handleDragStart={handleDragStart} + handleDragEnd={handleDragEnd} + handleDragOver={handleDragOver} + openProjectLabel={language.t("command.project.open")} + openProjectKeybind={() => command.keybind("project.open")} + onOpenProject={chooseProject} + renderProjectOverlay={() => ( + <ProjectDragOverlay + projects={() => layout.projects.list()} + activeProject={() => store.activeProject} + /> + )} + settingsLabel={() => language.t("sidebar.settings")} + settingsKeybind={() => command.keybind("settings.open")} + onOpenSettings={openSettings} + helpLabel={() => language.t("sidebar.help")} + onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")} + renderPanel={() => <SidebarPanel project={currentProject()} mobile />} + /> + </nav> </div> - </Show> - </nav> - <div - class="hidden xl:block pointer-events-none absolute top-0 right-0 z-0 border-t border-border-weaker-base" - style={{ left: "calc(4rem + 12px)" }} - /> + <div + classList={{ + "absolute inset-0": true, + "xl:inset-y-0 xl:right-0 xl:left-[var(--main-left)]": true, + "z-20": true, + "transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none": + !sizing(), + }} + style={{ + "--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem", + }} + > + <main + classList={{ + "size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base bg-background-base xl:border-l xl:rounded-tl-[12px]": true, + }} + > + <Show when={!autoselecting()} fallback={<div class="size-full" />}> + {props.children} + </Show> + </main> + </div> - <div class="xl:hidden"> - <div - classList={{ - "fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true, - "opacity-100 pointer-events-auto": layout.mobileSidebar.opened(), - "opacity-0 pointer-events-none": !layout.mobileSidebar.opened(), - }} - onClick={(e) => { - if (e.target === e.currentTarget) layout.mobileSidebar.hide() - }} - /> - <nav - aria-label={language.t("sidebar.nav.projectsAndSessions")} - data-component="sidebar-nav-mobile" - classList={{ - "@container fixed top-10 bottom-0 left-0 z-50 w-full max-w-[400px] overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true, - "translate-x-0": layout.mobileSidebar.opened(), - "-translate-x-full": !layout.mobileSidebar.opened(), - }} - onClick={(e) => e.stopPropagation()} - > - <SidebarContent - mobile - opened={() => layout.sidebar.opened()} - aimMove={aim.move} - projects={() => layout.projects.list()} - renderProject={(project) => ( - <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile /> - )} - handleDragStart={handleDragStart} - handleDragEnd={handleDragEnd} - handleDragOver={handleDragOver} - openProjectLabel={language.t("command.project.open")} - openProjectKeybind={() => command.keybind("project.open")} - onOpenProject={chooseProject} - renderProjectOverlay={() => ( - <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} /> - )} - settingsLabel={() => language.t("sidebar.settings")} - settingsKeybind={() => command.keybind("settings.open")} - onOpenSettings={openSettings} - helpLabel={() => language.t("sidebar.help")} - onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")} - renderPanel={() => <SidebarPanel project={currentProject()} mobile />} - /> - </nav> - </div> - - <div - classList={{ - "absolute inset-0": true, - "xl:inset-y-0 xl:right-0 xl:left-[var(--main-left)]": true, - "z-20": true, - "transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none": - !sizing(), - }} - style={{ - "--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem", - }} - > - <main - classList={{ - "size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base bg-background-base xl:border-l xl:rounded-tl-[12px]": true, - }} - > - <Show when={!autoselecting()} fallback={<div class="size-full" />}> - {props.children} - </Show> - </main> - </div> - - <div - classList={{ - "hidden xl:flex absolute inset-y-0 left-16 z-30": true, - "opacity-100 translate-x-0 pointer-events-auto": peeked() && !layout.sidebar.opened(), - "opacity-0 -translate-x-2 pointer-events-none": !peeked() || layout.sidebar.opened(), - "transition-[opacity,transform] motion-reduce:transition-none": true, - "duration-180 ease-out": peeked() && !layout.sidebar.opened(), - "duration-120 ease-in": !peeked() || layout.sidebar.opened(), - }} - onMouseMove={disarm} - onMouseEnter={() => { - disarm() - aim.reset() - }} - onPointerDown={disarm} - onMouseLeave={() => { - arm() - }} - > - <Show when={peek()} keyed> - {(project) => <SidebarPanel project={project} merged={false} />} - </Show> - </div> - - <div - classList={{ - "hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true, - "opacity-100 translate-x-0": peeked() && !layout.sidebar.opened(), - "opacity-0 -translate-x-2": !peeked() || layout.sidebar.opened(), - "transition-[opacity,transform] motion-reduce:transition-none": true, - "duration-180 ease-out": peeked() && !layout.sidebar.opened(), - "duration-120 ease-in": !peeked() || layout.sidebar.opened(), - }} - style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }} - > - <div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} /> + <div + classList={{ + "hidden xl:flex absolute inset-y-0 left-16 z-30": true, + "opacity-100 translate-x-0 pointer-events-auto": peeked() && !layout.sidebar.opened(), + "opacity-0 -translate-x-2 pointer-events-none": !peeked() || layout.sidebar.opened(), + "transition-[opacity,transform] motion-reduce:transition-none": true, + "duration-180 ease-out": peeked() && !layout.sidebar.opened(), + "duration-120 ease-in": !peeked() || layout.sidebar.opened(), + }} + onMouseMove={disarm} + onMouseEnter={() => { + disarm() + aim.reset() + }} + onPointerDown={disarm} + onMouseLeave={() => { + arm() + }} + > + <Show when={peek()} keyed> + {(project) => <SidebarPanel project={project} merged={false} />} + </Show> + </div> + + <div + classList={{ + "hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true, + "opacity-100 translate-x-0": peeked() && !layout.sidebar.opened(), + "opacity-0 -translate-x-2": !peeked() || layout.sidebar.opened(), + "transition-[opacity,transform] motion-reduce:transition-none": true, + "duration-180 ease-out": peeked() && !layout.sidebar.opened(), + "duration-120 ease-in": !peeked() || layout.sidebar.opened(), + }} + style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }} + > + <div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} /> + </div> + </div> </div> + {import.meta.env.DEV && <DebugBar />} </div> <Toast.Region /> </div> From 89d6f60d254028834ba340958d373c0f3199f631 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Mon, 9 Mar 2026 17:13:42 -0400 Subject: [PATCH 039/200] refactor(server): extract createApp function for server initialization - Replace Server.App() with Server.Default() for internal server access - Extract server app creation into Server.createApp(opts) for testability - Move CORS whitelist from module-level variable to function parameter - Update all tests to use Server.Default() instead of Server.App() --- packages/opencode/src/cli/cmd/run.ts | 2 +- packages/opencode/src/cli/cmd/tui/worker.ts | 4 +- packages/opencode/src/plugin/index.ts | 7 +- packages/opencode/src/server/server.ts | 974 +++++++++--------- .../test/server/project-init-git.test.ts | 4 +- .../test/server/session-select.test.ts | 6 +- 6 files changed, 493 insertions(+), 504 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index ab30e27148..f92d3305bf 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -667,7 +667,7 @@ export const RunCommand = cmd({ await bootstrap(process.cwd(), async () => { const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => { const request = new Request(input, init) - return Server.App().fetch(request) + return Server.Default().fetch(request) }) as typeof globalThis.fetch const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn }) await execute(sdk) diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index f8dcee78a0..408350c520 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -54,7 +54,7 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) => const request = new Request(input, init) const auth = getAuthorizationHeader() if (auth) request.headers.set("Authorization", auth) - return Server.App().fetch(request) + return Server.Default().fetch(request) }) as typeof globalThis.fetch const sdk = createOpencodeClient({ @@ -110,7 +110,7 @@ export const rpc = { headers, body: input.body, }) - const response = await Server.App().fetch(request) + const response = await Server.Default().fetch(request) const body = await response.text() return { status: response.status, diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index e65d21bfd6..1c129f6082 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -25,8 +25,7 @@ export namespace Plugin { const client = createOpencodeClient({ baseUrl: "http://localhost:4096", directory: Instance.directory, - // @ts-ignore - fetch type incompatibility - fetch: async (...args) => Server.App().fetch(...args), + fetch: async (...args) => Server.Default().fetch(...args), }) const config = await Config.get() const hooks: Hooks[] = [] @@ -35,7 +34,9 @@ export namespace Plugin { project: Instance.project, worktree: Instance.worktree, directory: Instance.directory, - serverUrl: Server.url(), + get serverUrl(): URL { + throw new Error("Server URL is no longer supported in plugins") + }, $: Bun.$, } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index e353198af7..3d435c8c99 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -31,7 +31,6 @@ import { FileRoutes } from "./routes/file" import { ConfigRoutes } from "./routes/config" import { ExperimentalRoutes } from "./routes/experimental" import { ProviderRoutes } from "./routes/provider" -import { lazy } from "../util/lazy" import { InstanceBootstrap } from "../project/bootstrap" import { NotFoundError } from "../storage/db" import type { ContentfulStatusCode } from "hono/utils/http-status" @@ -43,6 +42,7 @@ import { QuestionRoutes } from "./routes/question" import { PermissionRoutes } from "./routes/permission" import { GlobalRoutes } from "./routes/global" import { MDNS } from "./mdns" +import { lazy } from "@/util/lazy" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false @@ -50,538 +50,529 @@ globalThis.AI_SDK_LOG_WARNINGS = false export namespace Server { const log = Log.create({ service: "server" }) - let _url: URL | undefined - let _corsWhitelist: string[] = [] + export const Default = lazy(() => createApp({})) - export function url(): URL { - return _url ?? new URL("http://localhost:4096") - } - - const app = new Hono() - export const App: () => Hono = lazy( - () => - // TODO: Break server.ts into smaller route files to fix type inference - app - .onError((err, c) => { - log.error("failed", { - error: err, - }) - if (err instanceof NamedError) { - let status: ContentfulStatusCode - if (err instanceof NotFoundError) status = 404 - else if (err instanceof Provider.ModelNotFoundError) status = 400 - else if (err.name.startsWith("Worktree")) status = 400 - else status = 500 - return c.json(err.toObject(), { status }) - } - if (err instanceof HTTPException) return err.getResponse() - const message = err instanceof Error && err.stack ? err.stack : err.toString() - return c.json(new NamedError.Unknown({ message }).toObject(), { - status: 500, - }) + export const createApp = (opts: { cors?: string[] }): Hono => { + const app = new Hono() + return app + .onError((err, c) => { + log.error("failed", { + error: err, }) - .use((c, next) => { - // Allow CORS preflight requests to succeed without auth. - // Browser clients sending Authorization headers will preflight with OPTIONS. - if (c.req.method === "OPTIONS") return next() - const password = Flag.OPENCODE_SERVER_PASSWORD - if (!password) return next() - const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode" - return basicAuth({ username, password })(c, next) + if (err instanceof NamedError) { + let status: ContentfulStatusCode + if (err instanceof NotFoundError) status = 404 + else if (err instanceof Provider.ModelNotFoundError) status = 400 + else if (err.name.startsWith("Worktree")) status = 400 + else status = 500 + return c.json(err.toObject(), { status }) + } + if (err instanceof HTTPException) return err.getResponse() + const message = err instanceof Error && err.stack ? err.stack : err.toString() + return c.json(new NamedError.Unknown({ message }).toObject(), { + status: 500, }) - .use(async (c, next) => { - const skipLogging = c.req.path === "/log" - if (!skipLogging) { - log.info("request", { - method: c.req.method, - path: c.req.path, - }) - } - const timer = log.time("request", { + }) + .use((c, next) => { + // Allow CORS preflight requests to succeed without auth. + // Browser clients sending Authorization headers will preflight with OPTIONS. + if (c.req.method === "OPTIONS") return next() + const password = Flag.OPENCODE_SERVER_PASSWORD + if (!password) return next() + const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode" + return basicAuth({ username, password })(c, next) + }) + .use(async (c, next) => { + const skipLogging = c.req.path === "/log" + if (!skipLogging) { + log.info("request", { method: c.req.method, path: c.req.path, }) - await next() - if (!skipLogging) { - timer.stop() - } + } + const timer = log.time("request", { + method: c.req.method, + path: c.req.path, }) - .use( - cors({ - origin(input) { - if (!input) return + await next() + if (!skipLogging) { + timer.stop() + } + }) + .use( + cors({ + origin(input) { + if (!input) return - if (input.startsWith("http://localhost:")) return input - if (input.startsWith("http://127.0.0.1:")) return input - if ( - input === "tauri://localhost" || - input === "http://tauri.localhost" || - input === "https://tauri.localhost" - ) - return input + if (input.startsWith("http://localhost:")) return input + if (input.startsWith("http://127.0.0.1:")) return input + if ( + input === "tauri://localhost" || + input === "http://tauri.localhost" || + input === "https://tauri.localhost" + ) + return input - // *.opencode.ai (https only, adjust if needed) - if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) { - return input - } - if (_corsWhitelist.includes(input)) { - return input - } - - return - }, - }), - ) - .route("/global", GlobalRoutes()) - .put( - "/auth/:providerID", - describeRoute({ - summary: "Set auth credentials", - description: "Set authentication credentials", - operationId: "auth.set", - responses: { - 200: { - description: "Successfully set authentication credentials", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - ...errors(400), - }, - }), - validator( - "param", - z.object({ - providerID: z.string(), - }), - ), - validator("json", Auth.Info), - async (c) => { - const providerID = c.req.valid("param").providerID - const info = c.req.valid("json") - await Auth.set(providerID, info) - return c.json(true) - }, - ) - .delete( - "/auth/:providerID", - describeRoute({ - summary: "Remove auth credentials", - description: "Remove authentication credentials", - operationId: "auth.remove", - responses: { - 200: { - description: "Successfully removed authentication credentials", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - ...errors(400), - }, - }), - validator( - "param", - z.object({ - providerID: z.string(), - }), - ), - async (c) => { - const providerID = c.req.valid("param").providerID - await Auth.remove(providerID) - return c.json(true) - }, - ) - .use(async (c, next) => { - if (c.req.path === "/log") return next() - const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") - const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() - const directory = Filesystem.resolve( - (() => { - try { - return decodeURIComponent(raw) - } catch { - return raw - } - })(), - ) - - return WorkspaceContext.provide({ - workspaceID, - async fn() { - return Instance.provide({ - directory, - init: InstanceBootstrap, - async fn() { - return next() - }, - }) - }, - }) - }) - .use(WorkspaceRouterMiddleware) - .get( - "/doc", - openAPIRouteHandler(app, { - documentation: { - info: { - title: "opencode", - version: "0.0.3", - description: "opencode api", - }, - openapi: "3.1.1", - }, - }), - ) - .use( - validator( - "query", - z.object({ - directory: z.string().optional(), - workspace: z.string().optional(), - }), - ), - ) - .route("/project", ProjectRoutes()) - .route("/pty", PtyRoutes()) - .route("/config", ConfigRoutes()) - .route("/experimental", ExperimentalRoutes()) - .route("/session", SessionRoutes()) - .route("/permission", PermissionRoutes()) - .route("/question", QuestionRoutes()) - .route("/provider", ProviderRoutes()) - .route("/", FileRoutes()) - .route("/mcp", McpRoutes()) - .route("/tui", TuiRoutes()) - .post( - "/instance/dispose", - describeRoute({ - summary: "Dispose instance", - description: "Clean up and dispose the current OpenCode instance, releasing all resources.", - operationId: "instance.dispose", - responses: { - 200: { - description: "Instance disposed", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - }, - }), - async (c) => { - await Instance.dispose() - return c.json(true) - }, - ) - .get( - "/path", - describeRoute({ - summary: "Get paths", - description: - "Retrieve the current working directory and related path information for the OpenCode instance.", - operationId: "path.get", - responses: { - 200: { - description: "Path", - content: { - "application/json": { - schema: resolver( - z - .object({ - home: z.string(), - state: z.string(), - config: z.string(), - worktree: z.string(), - directory: z.string(), - }) - .meta({ - ref: "Path", - }), - ), - }, - }, - }, - }, - }), - async (c) => { - return c.json({ - home: Global.Path.home, - state: Global.Path.state, - config: Global.Path.config, - worktree: Instance.worktree, - directory: Instance.directory, - }) - }, - ) - .get( - "/vcs", - describeRoute({ - summary: "Get VCS info", - description: - "Retrieve version control system (VCS) information for the current project, such as git branch.", - operationId: "vcs.get", - responses: { - 200: { - description: "VCS info", - content: { - "application/json": { - schema: resolver(Vcs.Info), - }, - }, - }, - }, - }), - async (c) => { - const branch = await Vcs.branch() - return c.json({ - branch, - }) - }, - ) - .get( - "/command", - describeRoute({ - summary: "List commands", - description: "Get a list of all available commands in the OpenCode system.", - operationId: "command.list", - responses: { - 200: { - description: "List of commands", - content: { - "application/json": { - schema: resolver(Command.Info.array()), - }, - }, - }, - }, - }), - async (c) => { - const commands = await Command.list() - return c.json(commands) - }, - ) - .post( - "/log", - describeRoute({ - summary: "Write log", - description: "Write a log entry to the server logs with specified level and metadata.", - operationId: "app.log", - responses: { - 200: { - description: "Log entry written successfully", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - ...errors(400), - }, - }), - validator( - "json", - z.object({ - service: z.string().meta({ description: "Service name for the log entry" }), - level: z.enum(["debug", "info", "error", "warn"]).meta({ description: "Log level" }), - message: z.string().meta({ description: "Log message" }), - extra: z - .record(z.string(), z.any()) - .optional() - .meta({ description: "Additional metadata for the log entry" }), - }), - ), - async (c) => { - const { service, level, message, extra } = c.req.valid("json") - const logger = Log.create({ service }) - - switch (level) { - case "debug": - logger.debug(message, extra) - break - case "info": - logger.info(message, extra) - break - case "error": - logger.error(message, extra) - break - case "warn": - logger.warn(message, extra) - break + // *.opencode.ai (https only, adjust if needed) + if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) { + return input + } + if (opts?.cors?.includes(input)) { + return input } - return c.json(true) + return }, - ) - .get( - "/agent", - describeRoute({ - summary: "List agents", - description: "Get a list of all available AI agents in the OpenCode system.", - operationId: "app.agents", - responses: { - 200: { - description: "List of agents", - content: { - "application/json": { - schema: resolver(Agent.Info.array()), - }, + }), + ) + .route("/global", GlobalRoutes()) + .put( + "/auth/:providerID", + describeRoute({ + summary: "Set auth credentials", + description: "Set authentication credentials", + operationId: "auth.set", + responses: { + 200: { + description: "Successfully set authentication credentials", + content: { + "application/json": { + schema: resolver(z.boolean()), }, }, }, - }), - async (c) => { - const modes = await Agent.list() - return c.json(modes) + ...errors(400), }, - ) - .get( - "/skill", - describeRoute({ - summary: "List skills", - description: "Get a list of all available skills in the OpenCode system.", - operationId: "app.skills", - responses: { - 200: { - description: "List of skills", - content: { - "application/json": { - schema: resolver(Skill.Info.array()), - }, + }), + validator( + "param", + z.object({ + providerID: z.string(), + }), + ), + validator("json", Auth.Info), + async (c) => { + const providerID = c.req.valid("param").providerID + const info = c.req.valid("json") + await Auth.set(providerID, info) + return c.json(true) + }, + ) + .delete( + "/auth/:providerID", + describeRoute({ + summary: "Remove auth credentials", + description: "Remove authentication credentials", + operationId: "auth.remove", + responses: { + 200: { + description: "Successfully removed authentication credentials", + content: { + "application/json": { + schema: resolver(z.boolean()), }, }, }, - }), - async (c) => { - const skills = await Skill.all() - return c.json(skills) + ...errors(400), }, + }), + validator( + "param", + z.object({ + providerID: z.string(), + }), + ), + async (c) => { + const providerID = c.req.valid("param").providerID + await Auth.remove(providerID) + return c.json(true) + }, + ) + .use(async (c, next) => { + if (c.req.path === "/log") return next() + const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") + const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() + const directory = Filesystem.resolve( + (() => { + try { + return decodeURIComponent(raw) + } catch { + return raw + } + })(), ) - .get( - "/lsp", - describeRoute({ - summary: "Get LSP status", - description: "Get LSP server status", - operationId: "lsp.status", - responses: { - 200: { - description: "LSP server status", - content: { - "application/json": { - schema: resolver(LSP.Status.array()), - }, + + return WorkspaceContext.provide({ + workspaceID, + async fn() { + return Instance.provide({ + directory, + init: InstanceBootstrap, + async fn() { + return next() + }, + }) + }, + }) + }) + .use(WorkspaceRouterMiddleware) + .get( + "/doc", + openAPIRouteHandler(app, { + documentation: { + info: { + title: "opencode", + version: "0.0.3", + description: "opencode api", + }, + openapi: "3.1.1", + }, + }), + ) + .use( + validator( + "query", + z.object({ + directory: z.string().optional(), + workspace: z.string().optional(), + }), + ), + ) + .route("/project", ProjectRoutes()) + .route("/pty", PtyRoutes()) + .route("/config", ConfigRoutes()) + .route("/experimental", ExperimentalRoutes()) + .route("/session", SessionRoutes()) + .route("/permission", PermissionRoutes()) + .route("/question", QuestionRoutes()) + .route("/provider", ProviderRoutes()) + .route("/", FileRoutes()) + .route("/mcp", McpRoutes()) + .route("/tui", TuiRoutes()) + .post( + "/instance/dispose", + describeRoute({ + summary: "Dispose instance", + description: "Clean up and dispose the current OpenCode instance, releasing all resources.", + operationId: "instance.dispose", + responses: { + 200: { + description: "Instance disposed", + content: { + "application/json": { + schema: resolver(z.boolean()), }, }, }, - }), - async (c) => { - return c.json(await LSP.status()) }, - ) - .get( - "/formatter", - describeRoute({ - summary: "Get formatter status", - description: "Get formatter status", - operationId: "formatter.status", - responses: { - 200: { - description: "Formatter status", - content: { - "application/json": { - schema: resolver(Format.Status.array()), - }, + }), + async (c) => { + await Instance.dispose() + return c.json(true) + }, + ) + .get( + "/path", + describeRoute({ + summary: "Get paths", + description: "Retrieve the current working directory and related path information for the OpenCode instance.", + operationId: "path.get", + responses: { + 200: { + description: "Path", + content: { + "application/json": { + schema: resolver( + z + .object({ + home: z.string(), + state: z.string(), + config: z.string(), + worktree: z.string(), + directory: z.string(), + }) + .meta({ + ref: "Path", + }), + ), }, }, }, - }), - async (c) => { - return c.json(await Format.status()) }, - ) - .get( - "/event", - describeRoute({ - summary: "Subscribe to events", - description: "Get events", - operationId: "event.subscribe", - responses: { - 200: { - description: "Event stream", - content: { - "text/event-stream": { - schema: resolver(BusEvent.payloads()), - }, + }), + async (c) => { + return c.json({ + home: Global.Path.home, + state: Global.Path.state, + config: Global.Path.config, + worktree: Instance.worktree, + directory: Instance.directory, + }) + }, + ) + .get( + "/vcs", + describeRoute({ + summary: "Get VCS info", + description: "Retrieve version control system (VCS) information for the current project, such as git branch.", + operationId: "vcs.get", + responses: { + 200: { + description: "VCS info", + content: { + "application/json": { + schema: resolver(Vcs.Info), }, }, }, + }, + }), + async (c) => { + const branch = await Vcs.branch() + return c.json({ + branch, + }) + }, + ) + .get( + "/command", + describeRoute({ + summary: "List commands", + description: "Get a list of all available commands in the OpenCode system.", + operationId: "command.list", + responses: { + 200: { + description: "List of commands", + content: { + "application/json": { + schema: resolver(Command.Info.array()), + }, + }, + }, + }, + }), + async (c) => { + const commands = await Command.list() + return c.json(commands) + }, + ) + .post( + "/log", + describeRoute({ + summary: "Write log", + description: "Write a log entry to the server logs with specified level and metadata.", + operationId: "app.log", + responses: { + 200: { + description: "Log entry written successfully", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + ...errors(400), + }, + }), + validator( + "json", + z.object({ + service: z.string().meta({ description: "Service name for the log entry" }), + level: z.enum(["debug", "info", "error", "warn"]).meta({ description: "Log level" }), + message: z.string().meta({ description: "Log message" }), + extra: z + .record(z.string(), z.any()) + .optional() + .meta({ description: "Additional metadata for the log entry" }), }), - async (c) => { - log.info("event connected") - c.header("X-Accel-Buffering", "no") - c.header("X-Content-Type-Options", "nosniff") - return streamSSE(c, async (stream) => { + ), + async (c) => { + const { service, level, message, extra } = c.req.valid("json") + const logger = Log.create({ service }) + + switch (level) { + case "debug": + logger.debug(message, extra) + break + case "info": + logger.info(message, extra) + break + case "error": + logger.error(message, extra) + break + case "warn": + logger.warn(message, extra) + break + } + + return c.json(true) + }, + ) + .get( + "/agent", + describeRoute({ + summary: "List agents", + description: "Get a list of all available AI agents in the OpenCode system.", + operationId: "app.agents", + responses: { + 200: { + description: "List of agents", + content: { + "application/json": { + schema: resolver(Agent.Info.array()), + }, + }, + }, + }, + }), + async (c) => { + const modes = await Agent.list() + return c.json(modes) + }, + ) + .get( + "/skill", + describeRoute({ + summary: "List skills", + description: "Get a list of all available skills in the OpenCode system.", + operationId: "app.skills", + responses: { + 200: { + description: "List of skills", + content: { + "application/json": { + schema: resolver(Skill.Info.array()), + }, + }, + }, + }, + }), + async (c) => { + const skills = await Skill.all() + return c.json(skills) + }, + ) + .get( + "/lsp", + describeRoute({ + summary: "Get LSP status", + description: "Get LSP server status", + operationId: "lsp.status", + responses: { + 200: { + description: "LSP server status", + content: { + "application/json": { + schema: resolver(LSP.Status.array()), + }, + }, + }, + }, + }), + async (c) => { + return c.json(await LSP.status()) + }, + ) + .get( + "/formatter", + describeRoute({ + summary: "Get formatter status", + description: "Get formatter status", + operationId: "formatter.status", + responses: { + 200: { + description: "Formatter status", + content: { + "application/json": { + schema: resolver(Format.Status.array()), + }, + }, + }, + }, + }), + async (c) => { + return c.json(await Format.status()) + }, + ) + .get( + "/event", + describeRoute({ + summary: "Subscribe to events", + description: "Get events", + operationId: "event.subscribe", + responses: { + 200: { + description: "Event stream", + content: { + "text/event-stream": { + schema: resolver(BusEvent.payloads()), + }, + }, + }, + }, + }), + async (c) => { + log.info("event connected") + c.header("X-Accel-Buffering", "no") + c.header("X-Content-Type-Options", "nosniff") + return streamSSE(c, async (stream) => { + stream.writeSSE({ + data: JSON.stringify({ + type: "server.connected", + properties: {}, + }), + }) + const unsub = Bus.subscribeAll(async (event) => { + await stream.writeSSE({ + data: JSON.stringify(event), + }) + if (event.type === Bus.InstanceDisposed.type) { + stream.close() + } + }) + + // Send heartbeat every 10s to prevent stalled proxy streams. + const heartbeat = setInterval(() => { stream.writeSSE({ data: JSON.stringify({ - type: "server.connected", + type: "server.heartbeat", properties: {}, }), }) - const unsub = Bus.subscribeAll(async (event) => { - await stream.writeSSE({ - data: JSON.stringify(event), - }) - if (event.type === Bus.InstanceDisposed.type) { - stream.close() - } - }) + }, 10_000) - // Send heartbeat every 10s to prevent stalled proxy streams. - const heartbeat = setInterval(() => { - stream.writeSSE({ - data: JSON.stringify({ - type: "server.heartbeat", - properties: {}, - }), - }) - }, 10_000) - - await new Promise<void>((resolve) => { - stream.onAbort(() => { - clearInterval(heartbeat) - unsub() - resolve() - log.info("event disconnected") - }) + await new Promise<void>((resolve) => { + stream.onAbort(() => { + clearInterval(heartbeat) + unsub() + resolve() + log.info("event disconnected") }) }) - }, - ) - .all("/*", async (c) => { - const path = c.req.path - - const response = await proxy(`https://app.opencode.ai${path}`, { - ...c.req, - headers: { - ...c.req.raw.headers, - host: "app.opencode.ai", - }, }) - response.headers.set( - "Content-Security-Policy", - "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:", - ) - return response - }) as unknown as Hono, - ) + }, + ) + .all("/*", async (c) => { + const path = c.req.path + + const response = await proxy(`https://app.opencode.ai${path}`, { + ...c.req, + headers: { + ...c.req.raw.headers, + host: "app.opencode.ai", + }, + }) + response.headers.set( + "Content-Security-Policy", + "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:", + ) + return response + }) + } export async function openapi() { // Cast to break excessive type recursion from long route chains - const result = await generateSpecs(App() as Hono, { + const result = await generateSpecs(Default(), { documentation: { info: { title: "opencode", @@ -601,12 +592,11 @@ export namespace Server { mdnsDomain?: string cors?: string[] }) { - _corsWhitelist = opts.cors ?? [] - + const app = createApp(opts) const args = { hostname: opts.hostname, idleTimeout: 0, - fetch: App().fetch, + fetch: app.fetch, websocket: websocket, } as const const tryServe = (port: number) => { @@ -619,8 +609,6 @@ export namespace Server { const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port) if (!server) throw new Error(`Failed to start server on port ${opts.port}`) - _url = server.url - const shouldPublishMDNS = opts.mdns && server.port && diff --git a/packages/opencode/test/server/project-init-git.test.ts b/packages/opencode/test/server/project-init-git.test.ts index 50cda0ab58..cc1ac0cbc9 100644 --- a/packages/opencode/test/server/project-init-git.test.ts +++ b/packages/opencode/test/server/project-init-git.test.ts @@ -19,7 +19,7 @@ afterEach(async () => { describe("project.initGit endpoint", () => { test("initializes git and reloads immediately", async () => { await using tmp = await tmpdir() - const app = Server.App() + const app = Server.Default() const seen: { directory?: string; payload: { type: string } }[] = [] const fn = (evt: { directory?: string; payload: { type: string } }) => { seen.push(evt) @@ -75,7 +75,7 @@ describe("project.initGit endpoint", () => { test("does not reload when the project is already git", async () => { await using tmp = await tmpdir({ git: true }) - const app = Server.App() + const app = Server.Default() const seen: { directory?: string; payload: { type: string } }[] = [] const fn = (evt: { directory?: string; payload: { type: string } }) => { seen.push(evt) diff --git a/packages/opencode/test/server/session-select.test.ts b/packages/opencode/test/server/session-select.test.ts index 479be4a17f..a336f8133c 100644 --- a/packages/opencode/test/server/session-select.test.ts +++ b/packages/opencode/test/server/session-select.test.ts @@ -17,7 +17,7 @@ describe("tui.selectSession endpoint", () => { const session = await Session.create({}) // #when - const app = Server.App() + const app = Server.Default() const response = await app.request("/tui/select-session", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -42,7 +42,7 @@ describe("tui.selectSession endpoint", () => { const nonExistentSessionID = "ses_nonexistent123" // #when - const app = Server.App() + const app = Server.Default() const response = await app.request("/tui/select-session", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -63,7 +63,7 @@ describe("tui.selectSession endpoint", () => { const invalidSessionID = "invalid_session_id" // #when - const app = Server.App() + const app = Server.Default() const response = await app.request("/tui/select-session", { method: "POST", headers: { "Content-Type": "application/json" }, From ff3bb7424dcf3f3996f61ae63554f59c32f4a2ad Mon Sep 17 00:00:00 2001 From: adam jones <domdomegg+git@gmail.com> Date: Tue, 10 Mar 2026 02:01:19 +0000 Subject: [PATCH 040/200] fix(mcp): fix OAuth auto-connect failing on first connection (#15547) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/mcp/index.ts | 9 +- packages/opencode/src/mcp/oauth-provider.ts | 15 +- .../test/mcp/oauth-auto-connect.test.ts | 197 ++++++++++++++++++ 3 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 packages/opencode/test/mcp/oauth-auto-connect.test.ts diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 0dca27d651..2c0fc1d376 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -396,8 +396,13 @@ export namespace MCP { } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)) - // Handle OAuth-specific errors - if (error instanceof UnauthorizedError) { + // Handle OAuth-specific errors. + // The SDK throws UnauthorizedError when auth() returns 'REDIRECT', + // but may also throw plain Errors when auth() fails internally + // (e.g. during discovery, registration, or state generation). + // When an authProvider is attached, treat both cases as auth-related. + const isAuthError = error instanceof UnauthorizedError || (authProvider && lastError.message.includes("OAuth")) + if (isAuthError) { log.info("mcp server requires authentication", { key, transport: name }) // Check if this is a "needs registration" error diff --git a/packages/opencode/src/mcp/oauth-provider.ts b/packages/opencode/src/mcp/oauth-provider.ts index 164b1d1f14..b4da73169e 100644 --- a/packages/opencode/src/mcp/oauth-provider.ts +++ b/packages/opencode/src/mcp/oauth-provider.ts @@ -144,10 +144,19 @@ export class McpOAuthProvider implements OAuthClientProvider { async state(): Promise<string> { const entry = await McpAuth.get(this.mcpName) - if (!entry?.oauthState) { - throw new Error(`No OAuth state saved for MCP server: ${this.mcpName}`) + if (entry?.oauthState) { + return entry.oauthState } - return entry.oauthState + + // Generate a new state if none exists — the SDK calls state() as a + // generator, not just a reader, so we need to produce a value even when + // startAuth() hasn't pre-saved one (e.g. during automatic auth on first + // connect). + const newState = Array.from(crypto.getRandomValues(new Uint8Array(32))) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + await McpAuth.updateOAuthState(this.mcpName, newState) + return newState } async invalidateCredentials(type: "all" | "client" | "tokens"): Promise<void> { diff --git a/packages/opencode/test/mcp/oauth-auto-connect.test.ts b/packages/opencode/test/mcp/oauth-auto-connect.test.ts new file mode 100644 index 0000000000..0cd5c36e52 --- /dev/null +++ b/packages/opencode/test/mcp/oauth-auto-connect.test.ts @@ -0,0 +1,197 @@ +import { test, expect, mock, beforeEach } from "bun:test" + +// Mock UnauthorizedError to match the SDK's class +class MockUnauthorizedError extends Error { + constructor(message?: string) { + super(message ?? "Unauthorized") + this.name = "UnauthorizedError" + } +} + +// Track what options were passed to each transport constructor +const transportCalls: Array<{ + type: "streamable" | "sse" + url: string + options: { authProvider?: unknown } +}> = [] + +// Controls whether the mock transport simulates a 401 that triggers the SDK +// auth flow (which calls provider.state()) or a simple UnauthorizedError. +let simulateAuthFlow = true + +// Mock the transport constructors to simulate OAuth auto-auth on 401 +mock.module("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ + StreamableHTTPClientTransport: class MockStreamableHTTP { + authProvider: { + state?: () => Promise<string> + redirectToAuthorization?: (url: URL) => Promise<void> + saveCodeVerifier?: (v: string) => Promise<void> + } | undefined + constructor(url: URL, options?: { authProvider?: unknown }) { + this.authProvider = options?.authProvider as typeof this.authProvider + transportCalls.push({ + type: "streamable", + url: url.toString(), + options: options ?? {}, + }) + } + async start() { + // Simulate what the real SDK transport does on 401: + // It calls auth() which eventually calls provider.state(), then + // provider.redirectToAuthorization(), then throws UnauthorizedError. + if (simulateAuthFlow && this.authProvider) { + // The SDK calls provider.state() to get the OAuth state parameter + if (this.authProvider.state) { + await this.authProvider.state() + } + // The SDK calls saveCodeVerifier before redirecting + if (this.authProvider.saveCodeVerifier) { + await this.authProvider.saveCodeVerifier("test-verifier") + } + // The SDK calls redirectToAuthorization to redirect the user + if (this.authProvider.redirectToAuthorization) { + await this.authProvider.redirectToAuthorization(new URL("https://auth.example.com/authorize?state=test")) + } + throw new MockUnauthorizedError() + } + throw new MockUnauthorizedError() + } + async finishAuth(_code: string) {} + }, +})) + +mock.module("@modelcontextprotocol/sdk/client/sse.js", () => ({ + SSEClientTransport: class MockSSE { + constructor(url: URL, options?: { authProvider?: unknown }) { + transportCalls.push({ + type: "sse", + url: url.toString(), + options: options ?? {}, + }) + } + async start() { + throw new Error("Mock SSE transport cannot connect") + } + }, +})) + +// Mock the MCP SDK Client +mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({ + Client: class MockClient { + async connect(transport: { start: () => Promise<void> }) { + await transport.start() + } + }, +})) + +// Mock UnauthorizedError in the auth module so instanceof checks work +mock.module("@modelcontextprotocol/sdk/client/auth.js", () => ({ + UnauthorizedError: MockUnauthorizedError, +})) + +beforeEach(() => { + transportCalls.length = 0 + simulateAuthFlow = true +}) + +// Import modules after mocking +const { MCP } = await import("../../src/mcp/index") +const { Instance } = await import("../../src/project/instance") +const { tmpdir } = await import("../fixture/fixture") + +test("first connect to OAuth server shows needs_auth instead of failed", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + `${dir}/opencode.json`, + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + mcp: { + "test-oauth": { + type: "remote", + url: "https://example.com/mcp", + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const result = await MCP.add("test-oauth", { + type: "remote", + url: "https://example.com/mcp", + }) + + const serverStatus = result.status as Record<string, { status: string; error?: string }> + + // The server should be detected as needing auth, NOT as failed. + // Before the fix, provider.state() would throw a plain Error + // ("No OAuth state saved for MCP server: test-oauth") which was + // not caught as UnauthorizedError, causing status to be "failed". + expect(serverStatus["test-oauth"]).toBeDefined() + expect(serverStatus["test-oauth"].status).toBe("needs_auth") + }, + }) +}) + +test("state() generates a new state when none is saved", async () => { + const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider") + const { McpAuth } = await import("../../src/mcp/auth") + + await using tmp = await tmpdir() + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const provider = new McpOAuthProvider( + "test-state-gen", + "https://example.com/mcp", + {}, + { onRedirect: async () => {} }, + ) + + // Ensure no state exists + const entryBefore = await McpAuth.get("test-state-gen") + expect(entryBefore?.oauthState).toBeUndefined() + + // state() should generate and return a new state, not throw + const state = await provider.state() + expect(typeof state).toBe("string") + expect(state.length).toBe(64) // 32 bytes as hex + + // The generated state should be persisted + const entryAfter = await McpAuth.get("test-state-gen") + expect(entryAfter?.oauthState).toBe(state) + }, + }) +}) + +test("state() returns existing state when one is saved", async () => { + const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider") + const { McpAuth } = await import("../../src/mcp/auth") + + await using tmp = await tmpdir() + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const provider = new McpOAuthProvider( + "test-state-existing", + "https://example.com/mcp", + {}, + { onRedirect: async () => {} }, + ) + + // Pre-save a state + const existingState = "pre-saved-state-value" + await McpAuth.updateOAuthState("test-state-existing", existingState) + + // state() should return the existing state + const state = await provider.state() + expect(state).toBe(existingState) + }, + }) +}) From 4f982ddb94fc5ec4ed508f53b73ea0451095f107 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 02:02:18 +0000 Subject: [PATCH 041/200] chore: generate --- packages/opencode/src/mcp/index.ts | 3 ++- .../opencode/test/mcp/oauth-auto-connect.test.ts | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 2c0fc1d376..e48a42a8b3 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -401,7 +401,8 @@ export namespace MCP { // but may also throw plain Errors when auth() fails internally // (e.g. during discovery, registration, or state generation). // When an authProvider is attached, treat both cases as auth-related. - const isAuthError = error instanceof UnauthorizedError || (authProvider && lastError.message.includes("OAuth")) + const isAuthError = + error instanceof UnauthorizedError || (authProvider && lastError.message.includes("OAuth")) if (isAuthError) { log.info("mcp server requires authentication", { key, transport: name }) diff --git a/packages/opencode/test/mcp/oauth-auto-connect.test.ts b/packages/opencode/test/mcp/oauth-auto-connect.test.ts index 0cd5c36e52..76f825247c 100644 --- a/packages/opencode/test/mcp/oauth-auto-connect.test.ts +++ b/packages/opencode/test/mcp/oauth-auto-connect.test.ts @@ -22,11 +22,13 @@ let simulateAuthFlow = true // Mock the transport constructors to simulate OAuth auto-auth on 401 mock.module("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ StreamableHTTPClientTransport: class MockStreamableHTTP { - authProvider: { - state?: () => Promise<string> - redirectToAuthorization?: (url: URL) => Promise<void> - saveCodeVerifier?: (v: string) => Promise<void> - } | undefined + authProvider: + | { + state?: () => Promise<string> + redirectToAuthorization?: (url: URL) => Promise<void> + saveCodeVerifier?: (v: string) => Promise<void> + } + | undefined constructor(url: URL, options?: { authProvider?: unknown }) { this.authProvider = options?.authProvider as typeof this.authProvider transportCalls.push({ From 3500bf64b8e0a7f338c365832bfed4189c3e64cf Mon Sep 17 00:00:00 2001 From: Sebastian <hasta84@gmail.com> Date: Tue, 10 Mar 2026 11:03:05 +0100 Subject: [PATCH 042/200] upgrade opentui to v0.1.87 (#16772) --- bun.lock | 20 ++++++++++---------- packages/opencode/package.json | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bun.lock b/bun.lock index 4b7097232a..bb85a46e01 100644 --- a/bun.lock +++ b/bun.lock @@ -335,8 +335,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.86", - "@opentui/solid": "0.1.86", + "@opentui/core": "0.1.87", + "@opentui/solid": "0.1.87", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -1422,21 +1422,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.86", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.86", "@opentui/core-darwin-x64": "0.1.86", "@opentui/core-linux-arm64": "0.1.86", "@opentui/core-linux-x64": "0.1.86", "@opentui/core-win32-arm64": "0.1.86", "@opentui/core-win32-x64": "0.1.86", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-3tRLbI9ADrQE1jEEn4x2aJexEOQZkv9Emk2BixMZqxfVhz2zr2SxtpimDAX0vmZK3+GnWAwBWxuaCAsxZpY4+w=="], + "@opentui/core": ["@opentui/core@0.1.87", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.87", "@opentui/core-darwin-x64": "0.1.87", "@opentui/core-linux-arm64": "0.1.87", "@opentui/core-linux-x64": "0.1.87", "@opentui/core-win32-arm64": "0.1.87", "@opentui/core-win32-x64": "0.1.87", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-dhsmMv0IqKftwG7J/pBrLBj2armsYIg5R3LBvciRQI/6X89GufP4l1u0+QTACAx6iR4SYJJNVNQ2tdX8LM9rMw=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.86", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Zp7q64+d+Dcx6YrH3mRcnHq8EOBnrfc1RvjgSWLhpXr49hY6LzuhqpfZM57aGErPYlR+ff8QM6e5FUkFnDfyjw=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.87", "", { "os": "darwin", "cpu": "arm64" }, "sha512-G8oq85diOfkU6n0T1CxCle7oDmpKxwhcdhZ9khBMU5IrfLx9ZDuCM3F6MsiRQWdvPPCq2oomNbd64bYkPamYgw=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.86", "", { "os": "darwin", "cpu": "x64" }, "sha512-NcxfjCJm1kLnTMVOpAPdRYNi8W8XdAXNa6N7i9khiVFrl2v5KRQfUjbrSOUYVxFJNc3jKFG6rsn3jEApvn92qA=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.87", "", { "os": "darwin", "cpu": "x64" }, "sha512-MYTFQfOHm6qO7YaY4GHK9u/oJlXY6djaaxl5I+k4p2mk3vvuFIl/AP1ypITwBFjyV5gyp7PRWFp4nGfY9oN8bw=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.86", "", { "os": "linux", "cpu": "arm64" }, "sha512-EDHAvqSOr8CXzbDvo1aE5blJ6wu1aSbR2LqoXtoeXHemr2T2W42D2TdIWewG6K+/BuRbzZnqt9wnYFBksLW6lw=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.87", "", { "os": "linux", "cpu": "arm64" }, "sha512-he8o1h5M6oskRJ7wE+xKJgmWnv5ZwN6gB3M/Z+SeHtOMPa5cZmi3TefTjG54llEgFfx0F9RcqHof7TJ/GNxRkw=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.86", "", { "os": "linux", "cpu": "x64" }, "sha512-VBaBkVdQDxYV4WcKjb+jgyMS5PiVHepvfaoKWpz1Bq+J01xXW4XPcXyPGkgR1+2R93KzaugEnLscTW4mWtLHlQ=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.87", "", { "os": "linux", "cpu": "x64" }, "sha512-aiUwjPlH4yDcB8/6YDKSmMkaoGAAltL0Xo0AzXyAtJXWK5tkCSaYjEVwzJ/rYRkr4Magnad+Mjth4AQUWdR2AA=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.86", "", { "os": "win32", "cpu": "arm64" }, "sha512-xKbT7sEKYKGwUPkoqmLfHjbJU+vwHPDwf/r/mIunL41JXQBB35CSZ3/QgIwpp2kkteu7oE1tdBdg15ogUU4OMg=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.87", "", { "os": "win32", "cpu": "arm64" }, "sha512-cmP0pOyREjWGniHqbDmaMY7U+1AyagrD8VseJbU0cGpNgVpG2/gbrJUGdfdLB0SNb+mzLdx6SOjdxtrElwRCQA=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.86", "", { "os": "win32", "cpu": "x64" }, "sha512-HRfgAUlcu71/MrtgfX4Gj7PsDtfXZiuC506Pkn1OnRN1Xomcu10BVRDweUa0/g8ldU9i9kLjMGGnpw6/NjaBFg=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.87", "", { "os": "win32", "cpu": "x64" }, "sha512-N2GErAAP8iODf2RPp86pilPaVKiD6G4pkpZL5nLGbKsl0bndrVTpSqZcn8+/nQwFZDPD/AsiRTYNOfWOblhzOw=="], - "@opentui/solid": ["@opentui/solid@0.1.86", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.86", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-pOZC9dlZIH+bpstVVZ2AvYukBnslZTKSl/y5H8FWcMTHGv/BzpGxXBxstL65E/IQASqPFbvFcs7yMRzdLhynmA=="], + "@opentui/solid": ["@opentui/solid@0.1.87", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.87", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-lRT9t30l8+FtgOjjWJcdb2MT6hP8/RKqwGgYwTI7fXrOqdhxxwdP2SM+rH2l3suHeASheiTdlvPAo230iUcsvg=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 4e4f46b0c6..1c24be8a19 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -91,8 +91,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.86", - "@opentui/solid": "0.1.86", + "@opentui/core": "0.1.87", + "@opentui/solid": "0.1.87", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", From e7493e220494619f23e73774065b4e556743adae Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:16:15 +0000 Subject: [PATCH 043/200] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 2190feb256..d4146be6b7 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-+SMpaj0jeIHjlddAu6QIwojmWFVIiA8/G32hiQMjcOk=", - "aarch64-linux": "sha256-uo63IF6OCMab+O3ngn1sVxqIGJMm04HXuDgIRmXNTNk=", - "aarch64-darwin": "sha256-yB2tWm6AsX6UifnDqe7VldhN5zTQkDoqZ87AGQYjxT4=", - "x86_64-darwin": "sha256-nNhtqMSG4/y+uxjj14Jc5QQ7X6hQli9ni4v56XAvaAU=" + "x86_64-linux": "sha256-duBedS4ZTc1as03OM0KB9mKKU21Cywv4o9GHwQZv6Ts=", + "aarch64-linux": "sha256-juvQfuNBqqzeB/TIY9PuUDqgpsdyI54ImowjQLrNhns=", + "aarch64-darwin": "sha256-kKgcuEN1oJqHJc+sGjcZ4INWvbZczSTDJ8VHIWAquD4=", + "x86_64-darwin": "sha256-hXkFWOL4wi9s8HSrChpqtH4PKSNzbzVgU+0GbAxEUT4=" } } From d036c57d592331c1a2643b5d56457be033f3c927 Mon Sep 17 00:00:00 2001 From: bhaktatejas922 <bhaktatejas922@gmail.com> Date: Tue, 10 Mar 2026 04:00:13 -0700 Subject: [PATCH 044/200] docs: update opencode-morph-plugin in all language ecosystem pages (#16869) --- packages/web/src/content/docs/ar/ecosystem.mdx | 2 +- packages/web/src/content/docs/bs/ecosystem.mdx | 2 +- packages/web/src/content/docs/da/ecosystem.mdx | 2 +- packages/web/src/content/docs/de/ecosystem.mdx | 2 +- packages/web/src/content/docs/es/ecosystem.mdx | 2 +- packages/web/src/content/docs/fr/ecosystem.mdx | 2 +- packages/web/src/content/docs/it/ecosystem.mdx | 2 +- packages/web/src/content/docs/ja/ecosystem.mdx | 2 +- packages/web/src/content/docs/ko/ecosystem.mdx | 2 +- packages/web/src/content/docs/nb/ecosystem.mdx | 2 +- packages/web/src/content/docs/pl/ecosystem.mdx | 2 +- packages/web/src/content/docs/pt-br/ecosystem.mdx | 2 +- packages/web/src/content/docs/ru/ecosystem.mdx | 2 +- packages/web/src/content/docs/th/ecosystem.mdx | 2 +- packages/web/src/content/docs/tr/ecosystem.mdx | 2 +- packages/web/src/content/docs/zh-cn/ecosystem.mdx | 2 +- packages/web/src/content/docs/zh-tw/ecosystem.mdx | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/web/src/content/docs/ar/ecosystem.mdx b/packages/web/src/content/docs/ar/ecosystem.mdx index 1725daffc6..e2a664a1cd 100644 --- a/packages/web/src/content/docs/ar/ecosystem.mdx +++ b/packages/web/src/content/docs/ar/ecosystem.mdx @@ -32,7 +32,7 @@ description: مشاريع وتكاملات مبنية باستخدام OpenCode. | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | تعليمات لأوامر shell غير التفاعلية - تمنع التعليق الناتج عن العمليات المعتمدة على TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | تتبع استخدام OpenCode باستخدام Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | تنظيف جداول markdown التي تنتجها LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | تحرير الكود أسرع بـ 10 مرات باستخدام Morph Fast Apply API وعلامات التحرير الكسولة | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | تحرير Fast Apply وبحث WarpGrep في قاعدة الكود وضغط السياق عبر Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | وكلاء الخلفية، وأدوات LSP/AST/MCP المعدة مسبقًا، ووكلاء مختارون، متوافق مع Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | إشعارات سطح المكتب وتنبيهات صوتية لجلسات OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | إشعارات سطح المكتب وتنبيهات صوتية لأحداث الإذن والاكتمال والخطأ | diff --git a/packages/web/src/content/docs/bs/ecosystem.mdx b/packages/web/src/content/docs/bs/ecosystem.mdx index ff0fbc1526..f98c6433ce 100644 --- a/packages/web/src/content/docs/bs/ecosystem.mdx +++ b/packages/web/src/content/docs/bs/ecosystem.mdx @@ -32,7 +32,7 @@ Također možete pogledati [awesome-opencode](https://github.com/awesome-opencod | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Upute za neinteraktivne naredbe ljuske - sprječava visi od TTY ovisnih operacija | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Pratite upotrebu OpenCode sa Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Očistite tabele umanjenja vrijednosti koje su izradili LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x brže uređivanje koda s Morph Fast Apply API-jem i markerima za lijeno uređivanje | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply uređivanje, WarpGrep pretraga koda i kompresija konteksta putem Morph-a | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Pozadinski agenti, unapred izgrađeni LSP/AST/MCP alati, kurirani agenti, kompatibilni sa Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Obavještenja na radnoj površini i zvučna upozorenja za OpenCode sesije | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Obavještenja na radnoj površini i zvučna upozorenja za dozvole, završetak i događaje greške | diff --git a/packages/web/src/content/docs/da/ecosystem.mdx b/packages/web/src/content/docs/da/ecosystem.mdx index 0bba6ecddd..c0eebdbab3 100644 --- a/packages/web/src/content/docs/da/ecosystem.mdx +++ b/packages/web/src/content/docs/da/ecosystem.mdx @@ -32,7 +32,7 @@ Du kan også tjekke [awesome-opencode](https://github.com/awesome-opencode/aweso | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruktioner til ikke-interaktive shell-kommandoer - forhindrer hænger fra TTY-afhængige operationer | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode brug med Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ryd op afmærkningstabeller produceret af LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x hurtigere koderedigering med Morph Fast Apply API og dovne redigeringsmarkører | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøgning og kontekstkomprimering via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Baggrundsagenter, præbyggede LSP/AST/MCP værktøjer, kuraterede agenter, Claude Kodekompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsmeddelelser og lydadvarsler for OpenCode-sessioner | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsmeddelelser og lydadvarsler for tilladelser, fuldførelse og fejlhændelser | diff --git a/packages/web/src/content/docs/de/ecosystem.mdx b/packages/web/src/content/docs/de/ecosystem.mdx index c9ffcf9c68..802f83af55 100644 --- a/packages/web/src/content/docs/de/ecosystem.mdx +++ b/packages/web/src/content/docs/de/ecosystem.mdx @@ -32,7 +32,7 @@ Sie können sich auch [awesome-opencode](https://github.com/awesome-opencode/awe | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Anweisungen für nicht interaktive Shell-Befehle – verhindert Abstürze bei TTY-abhängigen Vorgängen | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Verfolgen Sie die Nutzung von OpenCode mit Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Von LLMs erstellte Abschriftentabellen bereinigen | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x schnellere Codebearbeitung mit Morph Fast Apply API und Lazy-Edit-Markern | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-Bearbeitung, WarpGrep-Codesuche und Kontextkomprimierung über Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Hintergrundagenten, vorgefertigte LSP/AST/MCP-Tools, kuratierte Agenten, Claude Code-kompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop-Benachrichtigungen und akustische Warnungen für OpenCode-Sitzungen | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop-Benachrichtigungen und akustische Warnungen für Berechtigungs-, Abschluss- und Fehlerereignisse | diff --git a/packages/web/src/content/docs/es/ecosystem.mdx b/packages/web/src/content/docs/es/ecosystem.mdx index 8bb32e7065..238e8c6ffa 100644 --- a/packages/web/src/content/docs/es/ecosystem.mdx +++ b/packages/web/src/content/docs/es/ecosystem.mdx @@ -32,7 +32,7 @@ También puedes consultar [awesome-opencode](https://github.com/awesome-opencode | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrucciones para comandos de shell no interactivos: evita bloqueos de operaciones dependientes de TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Seguimiento del uso de OpenCode con Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpiar tablas de Markdown producidas por LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edición de código 10 veces más rápida con Morph Fast Apply API y marcadores de edición diferidos | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Edición Fast Apply, búsqueda de código con WarpGrep y compactación de contexto a través de Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes en segundo plano, herramientas LSP/AST/MCP prediseñadas, agentes seleccionados, compatible con Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificaciones de escritorio y alertas sonoras para sesiones OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificaciones de escritorio y alertas sonoras para eventos de permiso, finalización y error | diff --git a/packages/web/src/content/docs/fr/ecosystem.mdx b/packages/web/src/content/docs/fr/ecosystem.mdx index 2ce2c52438..d578017bf9 100644 --- a/packages/web/src/content/docs/fr/ecosystem.mdx +++ b/packages/web/src/content/docs/fr/ecosystem.mdx @@ -32,7 +32,7 @@ Vous pouvez également consulter [awesome-opencode](https://github.com/awesome-o | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions pour les commandes shell non interactives - empêche les blocages dus aux opérations dépendantes du TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Suit l'utilisation d'OpenCode avec Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Nettoie les tableaux Markdown produits par les LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Édition de code 10x plus rapide avec l'API Morph Fast Apply et des marqueurs d'édition différée | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Édition Fast Apply, recherche de code WarpGrep et compaction de contexte via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agents d'arrière-plan, outils LSP/AST/MCP pré-construits, agents sélectionnés, compatible Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifications de bureau et alertes sonores pour les sessions OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifications de bureau et alertes sonores pour les événements de permission, d'achèvement et d'erreur | diff --git a/packages/web/src/content/docs/it/ecosystem.mdx b/packages/web/src/content/docs/it/ecosystem.mdx index d2cb9c4383..093c74b13e 100644 --- a/packages/web/src/content/docs/it/ecosystem.mdx +++ b/packages/web/src/content/docs/it/ecosystem.mdx @@ -32,7 +32,7 @@ Puoi anche dare un'occhiata a [awesome-opencode](https://github.com/awesome-open | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Istruzioni per comandi shell non interattivi: evita blocchi dovuti a operazioni dipendenti da TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Traccia l'uso di OpenCode con Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ripulisce le tabelle markdown prodotte dai LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Editing del codice 10x più veloce con Morph Fast Apply API e marker lazy edit | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Editing Fast Apply, ricerca codebase WarpGrep e compattazione del contesto tramite Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agenti in background, tool LSP/AST/MCP predefiniti, agenti curati, compatibile con Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifiche desktop e avvisi sonori per le sessioni OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifiche desktop e avvisi sonori per eventi di permesso, completamento ed errore | diff --git a/packages/web/src/content/docs/ja/ecosystem.mdx b/packages/web/src/content/docs/ja/ecosystem.mdx index b1a24a7dbf..02f3b76411 100644 --- a/packages/web/src/content/docs/ja/ecosystem.mdx +++ b/packages/web/src/content/docs/ja/ecosystem.mdx @@ -32,7 +32,7 @@ OpenCode 関連プロジェクトをこのリストに追加したいですか? | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非対話型シェルコマンドの手順 - TTY に依存する操作によるハングの防止 | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | wakatime で OpenCode の使用状況を追跡する | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM によって生成された Markdown テーブルをクリーンアップする | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast apply API と遅延編集マーカーにより 10 倍高速なコード編集 | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Morph による Fast Apply 編集、WarpGrep コードベース検索、コンテキスト圧縮 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | バックグラウンドエージェント、事前構築された LSP/AST/MCP ツール、厳選されたエージェント、Claude Code 互換 | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode セッションのデスクトップ通知とサウンドアラート | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 許可、完了、エラーイベントのデスクトップ通知とサウンドアラート | diff --git a/packages/web/src/content/docs/ko/ecosystem.mdx b/packages/web/src/content/docs/ko/ecosystem.mdx index 455459b7ca..eece60fb0d 100644 --- a/packages/web/src/content/docs/ko/ecosystem.mdx +++ b/packages/web/src/content/docs/ko/ecosystem.mdx @@ -32,7 +32,7 @@ OpenCode를 기반으로 만들어진 커뮤니티 프로젝트 모음입니다. | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 비대화형 shell 명령 실행 지침을 제공해 TTY 의존 작업으로 인한 멈춤을 방지합니다. | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime으로 OpenCode 사용량을 추적합니다. | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM이 생성한 markdown 표를 정리합니다. | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API와 lazy edit marker를 활용해 코드 편집 속도를 크게 높입니다. | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Morph를 통한 Fast Apply 편집, WarpGrep 코드베이스 검색 및 컨텍스트 압축 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | background agent, 사전 구성된 LSP/AST/MCP tool, curated agent, Claude Code 호환성을 제공합니다. | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 세션에 데스크톱 알림과 사운드 알림을 제공합니다. | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | permission, 완료, 오류 이벤트에 대한 데스크톱 알림과 사운드 알림을 제공합니다. | diff --git a/packages/web/src/content/docs/nb/ecosystem.mdx b/packages/web/src/content/docs/nb/ecosystem.mdx index 2c67b9fd8e..ab530c11dd 100644 --- a/packages/web/src/content/docs/nb/ecosystem.mdx +++ b/packages/web/src/content/docs/nb/ecosystem.mdx @@ -32,7 +32,7 @@ Du kan også sjekke ut [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruksjoner for ikke-interaktive skallkommandoer - forhindrer heng ved TTY-avhengige operasjoner | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode-bruk med Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Rydd opp i markdown-tabeller produsert av LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10 ganger raskere koderedigering med Morph Fast Apply API og lazy-redigeringsmarkører | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøk og kontekstkomprimering via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Bakgrunnsagenter, forhåndsbygde LSP/AST/MCP verktøy, kurerte agenter, Claude Code-kompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsvarsler og lydvarsler for OpenCode-økter | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsvarsler og lydvarsler for tillatelse, fullføring og feilhendelser | diff --git a/packages/web/src/content/docs/pl/ecosystem.mdx b/packages/web/src/content/docs/pl/ecosystem.mdx index 9dc4179b75..f7a9048e64 100644 --- a/packages/web/src/content/docs/pl/ecosystem.mdx +++ b/packages/web/src/content/docs/pl/ecosystem.mdx @@ -32,7 +32,7 @@ Możesz również sprawdzić [awesome-opencode](https://github.com/awesome-openc | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrukcje dla nieinteraktywnych poleceń powłoki - zapobiega zawieszeniom operacji zależnych od TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Śledź użycie OpenCode za pomocą Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Oczyść tabele markdown generowane przez LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x szybsza edycja kodu dzięki API Morph Fast Apply i leniwym znacznikom edycji | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Edycja Fast Apply, wyszukiwanie kodu WarpGrep i kompaktowanie kontekstu przez Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agenci w tle, wbudowane narzędzia LSP/AST/MCP, wyselekcjonowani agenci, kompatybilność z Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Powiadomienia na pulpicie i alerty dźwiękowe dla sesji OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Powiadomienia na pulpicie i alerty dźwiękowe dla uprawnień, zakończeń zadań i błędów | diff --git a/packages/web/src/content/docs/pt-br/ecosystem.mdx b/packages/web/src/content/docs/pt-br/ecosystem.mdx index 1d4d0bac11..3d94334e6b 100644 --- a/packages/web/src/content/docs/pt-br/ecosystem.mdx +++ b/packages/web/src/content/docs/pt-br/ecosystem.mdx @@ -32,7 +32,7 @@ Você também pode conferir [awesome-opencode](https://github.com/awesome-openco | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruções para comandos de shell não interativos - evita travamentos de operações dependentes de TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Acompanhe o uso do OpenCode com Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpe tabelas markdown produzidas por LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edição de código 10x mais rápida com a API Morph Fast Apply e marcadores de edição preguiçosos | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Edição Fast Apply, busca de código WarpGrep e compactação de contexto via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes em segundo plano, ferramentas LSP/AST/MCP pré-construídas, agentes curados, compatível com Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificações de desktop e alertas sonoros para sessões do OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificações de desktop e alertas sonoros para eventos de permissão, conclusão e erro | diff --git a/packages/web/src/content/docs/ru/ecosystem.mdx b/packages/web/src/content/docs/ru/ecosystem.mdx index e0068ead2c..81b0312bbb 100644 --- a/packages/web/src/content/docs/ru/ecosystem.mdx +++ b/packages/web/src/content/docs/ru/ecosystem.mdx @@ -32,7 +32,7 @@ description: Проекты и интеграции, созданные с по | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Инструкции для неинтерактивных shell-команд — предотвращают зависания из-за операций, зависящих от TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Отслеживайте использование OpenCode с помощью Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Очистка таблиц Markdown, созданных LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Редактирование кода в 10 раз быстрее с помощью API Morph Fast Apply и маркеров отложенного редактирования | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Редактирование Fast Apply, поиск по кодовой базе WarpGrep и сжатие контекста через Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Фоновые агенты, встроенные инструменты LSP/AST/MCP, курируемые агенты, совместимость с Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Уведомления на рабочем столе и звуковые оповещения для сеансов OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Уведомления на рабочем столе и звуковые оповещения о разрешениях, завершении и событиях ошибок | diff --git a/packages/web/src/content/docs/th/ecosystem.mdx b/packages/web/src/content/docs/th/ecosystem.mdx index a1b7e90397..dc192f310a 100644 --- a/packages/web/src/content/docs/th/ecosystem.mdx +++ b/packages/web/src/content/docs/th/ecosystem.mdx @@ -32,7 +32,7 @@ description: โปรเจ็กต์และการผสานรวม | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | คำแนะนำสำหรับคำสั่ง shell แบบไม่โต้ตอบ - ป้องกันการแฮงค์จากการดำเนินการที่ขึ้นอยู่กับ TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | ติดตามการใช้งาน OpenCode ด้วย Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | ทำความสะอาดตาราง Markdown ที่ผลิตโดย LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | การแก้ไขโค้ดเร็วขึ้น 10 เท่าด้วย Morph Fast Apply API และเครื่องหมายแก้ไขแบบ Lazy | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | การแก้ไข Fast Apply, การค้นหาโค้ดเบส WarpGrep และการบีบอัดบริบทผ่าน Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | ตัวแทนเบื้องหลัง, เครื่องมือ LSP/AST/MCP ที่สร้างไว้ล่วงหน้า, ตัวแทนที่ได้รับการดูแลจัดการ, เข้ากันได้กับ Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับเซสชัน OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับการอนุญาต การดำเนินการเสร็จสิ้น และเหตุการณ์ข้อผิดพลาด | diff --git a/packages/web/src/content/docs/tr/ecosystem.mdx b/packages/web/src/content/docs/tr/ecosystem.mdx index ba534c70b4..ca8bc409ca 100644 --- a/packages/web/src/content/docs/tr/ecosystem.mdx +++ b/packages/web/src/content/docs/tr/ecosystem.mdx @@ -32,7 +32,7 @@ Ayrıca ekosistemi ve topluluğu bir araya getiren [awesome-opencode](https://gi | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Etkileşimli olmayan kabuk komutları için talimatlar - TTY bağımlı işlemlerden kaynaklanan takılmaları önler | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime ile OpenCode kullanımını takip edin | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM'ler tarafından üretilen markdown tablolarını temizleyin | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API ve tembel düzenleme işaretçileriyle 10 kat daha hızlı kod düzenleme | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply düzenleme, WarpGrep kod tabanı araması ve Morph ile bağlam sıkıştırma | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Arka plan aracıları, hazır LSP/AST/MCP araçları, seçilmiş aracılar, Claude Code uyumlu | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode oturumları için masaüstü bildirimleri ve sesli uyarılar | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | İzin, tamamlanma ve hata olayları için masaüstü bildirimleri ve sesli uyarılar | diff --git a/packages/web/src/content/docs/zh-cn/ecosystem.mdx b/packages/web/src/content/docs/zh-cn/ecosystem.mdx index d222903d34..aaa6a3ede4 100644 --- a/packages/web/src/content/docs/zh-cn/ecosystem.mdx +++ b/packages/web/src/content/docs/zh-cn/ecosystem.mdx @@ -32,7 +32,7 @@ description: 基于 OpenCode 构建的项目与集成。 | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非交互式 shell 命令指令——防止依赖 TTY 的操作导致挂起 | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追踪 OpenCode 的使用情况 | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 通过 Morph Fast Apply API 和惰性编辑标记实现 10 倍更快的代码编辑 | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | 通过 Morph 提供 Fast Apply 编辑、WarpGrep 代码搜索和上下文压缩 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 后台代理、预构建的 LSP/AST/MCP 工具、精选代理,兼容 Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 会话的桌面通知和声音提醒 | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 针对权限请求、任务完成和错误事件的桌面通知与声音提醒 | diff --git a/packages/web/src/content/docs/zh-tw/ecosystem.mdx b/packages/web/src/content/docs/zh-tw/ecosystem.mdx index b44dfdacfd..99067acf46 100644 --- a/packages/web/src/content/docs/zh-tw/ecosystem.mdx +++ b/packages/web/src/content/docs/zh-tw/ecosystem.mdx @@ -32,7 +32,7 @@ description: 基於 OpenCode 建置的專案與整合。 | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非互動式 shell 指令說明——防止依賴 TTY 的操作導致卡住 | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追蹤 OpenCode 的使用情況 | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 透過 Morph Fast Apply API 和惰性編輯標記實現 10 倍更快的程式碼編輯 | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | 透過 Morph 提供 Fast Apply 編輯、WarpGrep 程式碼搜尋和上下文壓縮 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 背景代理、預建置的 LSP/AST/MCP 工具、精選代理,相容 Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 工作階段的桌面通知和聲音提醒 | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 針對權限請求、任務完成和錯誤事件的桌面通知與聲音提醒 | From d2c765e2b30e6cd455cc6c8781fd60ac806e66b8 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:01:22 +0000 Subject: [PATCH 045/200] chore: generate --- packages/web/src/content/docs/bs/ecosystem.mdx | 2 +- packages/web/src/content/docs/da/ecosystem.mdx | 2 +- packages/web/src/content/docs/ko/ecosystem.mdx | 2 +- packages/web/src/content/docs/nb/ecosystem.mdx | 2 +- packages/web/src/content/docs/th/ecosystem.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web/src/content/docs/bs/ecosystem.mdx b/packages/web/src/content/docs/bs/ecosystem.mdx index f98c6433ce..65a665ca9c 100644 --- a/packages/web/src/content/docs/bs/ecosystem.mdx +++ b/packages/web/src/content/docs/bs/ecosystem.mdx @@ -32,7 +32,7 @@ Također možete pogledati [awesome-opencode](https://github.com/awesome-opencod | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Upute za neinteraktivne naredbe ljuske - sprječava visi od TTY ovisnih operacija | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Pratite upotrebu OpenCode sa Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Očistite tabele umanjenja vrijednosti koje su izradili LLM | -| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply uređivanje, WarpGrep pretraga koda i kompresija konteksta putem Morph-a | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply uređivanje, WarpGrep pretraga koda i kompresija konteksta putem Morph-a | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Pozadinski agenti, unapred izgrađeni LSP/AST/MCP alati, kurirani agenti, kompatibilni sa Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Obavještenja na radnoj površini i zvučna upozorenja za OpenCode sesije | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Obavještenja na radnoj površini i zvučna upozorenja za dozvole, završetak i događaje greške | diff --git a/packages/web/src/content/docs/da/ecosystem.mdx b/packages/web/src/content/docs/da/ecosystem.mdx index c0eebdbab3..e8382216b2 100644 --- a/packages/web/src/content/docs/da/ecosystem.mdx +++ b/packages/web/src/content/docs/da/ecosystem.mdx @@ -32,7 +32,7 @@ Du kan også tjekke [awesome-opencode](https://github.com/awesome-opencode/aweso | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruktioner til ikke-interaktive shell-kommandoer - forhindrer hænger fra TTY-afhængige operationer | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode brug med Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ryd op afmærkningstabeller produceret af LLMs | -| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøgning og kontekstkomprimering via Morph | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøgning og kontekstkomprimering via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Baggrundsagenter, præbyggede LSP/AST/MCP værktøjer, kuraterede agenter, Claude Kodekompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsmeddelelser og lydadvarsler for OpenCode-sessioner | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsmeddelelser og lydadvarsler for tilladelser, fuldførelse og fejlhændelser | diff --git a/packages/web/src/content/docs/ko/ecosystem.mdx b/packages/web/src/content/docs/ko/ecosystem.mdx index eece60fb0d..546d7af31a 100644 --- a/packages/web/src/content/docs/ko/ecosystem.mdx +++ b/packages/web/src/content/docs/ko/ecosystem.mdx @@ -32,7 +32,7 @@ OpenCode를 기반으로 만들어진 커뮤니티 프로젝트 모음입니다. | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 비대화형 shell 명령 실행 지침을 제공해 TTY 의존 작업으로 인한 멈춤을 방지합니다. | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime으로 OpenCode 사용량을 추적합니다. | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM이 생성한 markdown 표를 정리합니다. | -| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Morph를 통한 Fast Apply 편집, WarpGrep 코드베이스 검색 및 컨텍스트 압축 | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Morph를 통한 Fast Apply 편집, WarpGrep 코드베이스 검색 및 컨텍스트 압축 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | background agent, 사전 구성된 LSP/AST/MCP tool, curated agent, Claude Code 호환성을 제공합니다. | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 세션에 데스크톱 알림과 사운드 알림을 제공합니다. | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | permission, 완료, 오류 이벤트에 대한 데스크톱 알림과 사운드 알림을 제공합니다. | diff --git a/packages/web/src/content/docs/nb/ecosystem.mdx b/packages/web/src/content/docs/nb/ecosystem.mdx index ab530c11dd..7cd0c21ab9 100644 --- a/packages/web/src/content/docs/nb/ecosystem.mdx +++ b/packages/web/src/content/docs/nb/ecosystem.mdx @@ -32,7 +32,7 @@ Du kan også sjekke ut [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruksjoner for ikke-interaktive skallkommandoer - forhindrer heng ved TTY-avhengige operasjoner | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode-bruk med Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Rydd opp i markdown-tabeller produsert av LLMs | -| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøk og kontekstkomprimering via Morph | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøk og kontekstkomprimering via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Bakgrunnsagenter, forhåndsbygde LSP/AST/MCP verktøy, kurerte agenter, Claude Code-kompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsvarsler og lydvarsler for OpenCode-økter | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsvarsler og lydvarsler for tillatelse, fullføring og feilhendelser | diff --git a/packages/web/src/content/docs/th/ecosystem.mdx b/packages/web/src/content/docs/th/ecosystem.mdx index dc192f310a..ccbf2f1907 100644 --- a/packages/web/src/content/docs/th/ecosystem.mdx +++ b/packages/web/src/content/docs/th/ecosystem.mdx @@ -32,7 +32,7 @@ description: โปรเจ็กต์และการผสานรวม | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | คำแนะนำสำหรับคำสั่ง shell แบบไม่โต้ตอบ - ป้องกันการแฮงค์จากการดำเนินการที่ขึ้นอยู่กับ TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | ติดตามการใช้งาน OpenCode ด้วย Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | ทำความสะอาดตาราง Markdown ที่ผลิตโดย LLM | -| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | การแก้ไข Fast Apply, การค้นหาโค้ดเบส WarpGrep และการบีบอัดบริบทผ่าน Morph | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | การแก้ไข Fast Apply, การค้นหาโค้ดเบส WarpGrep และการบีบอัดบริบทผ่าน Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | ตัวแทนเบื้องหลัง, เครื่องมือ LSP/AST/MCP ที่สร้างไว้ล่วงหน้า, ตัวแทนที่ได้รับการดูแลจัดการ, เข้ากันได้กับ Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับเซสชัน OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับการอนุญาต การดำเนินการเสร็จสิ้น และเหตุการณ์ข้อผิดพลาด | From 5ec5d1daceaab23c8ffa9ae32b40f53120f4609e Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 10 Mar 2026 07:05:49 -0500 Subject: [PATCH 046/200] chore(app): debug window --- packages/app/src/components/debug-bar.tsx | 25 +++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index 93a4654f86..acfd7f90f4 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -49,14 +49,19 @@ const bad = (n: number | undefined, limit: number, low = false) => { const session = (path: string) => path.includes("/session") -function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string }) { +function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string; wide?: boolean }) { return ( - <Tooltip value={props.tip} placement="left"> - <div class="flex w-full flex-col items-center px-0.5 py-1 text-center"> - <div class="text-[7px] font-black uppercase tracking-[0.04em] opacity-70 leading-none">{props.label}</div> + <Tooltip value={props.tip} placement="top"> + <div + classList={{ + "flex min-h-[42px] w-full min-w-0 flex-col items-center justify-center rounded-[8px] bg-white/5 px-0.5 py-1 text-center": true, + "col-span-2": !!props.wide, + }} + > + <div class="text-[10px] leading-none font-black uppercase tracking-[0.04em] opacity-70">{props.label}</div> <div classList={{ - "text-[9px] font-semibold leading-none tabular-nums": true, + "text-[13px] leading-none font-bold tabular-nums sm:text-[14px]": true, "text-text-on-critical-base": !!props.bad, "opacity-70": !!props.dim, }} @@ -355,10 +360,13 @@ export function DebugBar() { return ( <aside aria-label="Development performance diagnostics" - class="pointer-events-auto h-full min-h-0 w-[36px] shrink-0 overflow-y-auto text-text-on-interactive-base no-scrollbar sm:w-[38px]" - style={{ "background-color": "color-mix(in srgb, var(--icon-interactive-base) 42%, black)" }} + class="pointer-events-auto fixed bottom-3 right-3 z-50 w-[308px] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-xl border p-0.5 text-text-on-interactive-base shadow-[var(--shadow-lg-border-base)] sm:bottom-4 sm:right-4 sm:w-[324px]" + style={{ + "background-color": "color-mix(in srgb, var(--icon-interactive-base) 42%, black)", + "border-color": "color-mix(in srgb, white 14%, transparent)", + }} > - <div class="flex min-h-full flex-col gap-0.5 py-2 font-mono"> + <div class="grid grid-cols-5 gap-px font-mono"> <Cell label="NAV" tip="Last completed route transition touching a session page, measured from router start until the first paint after it settles." @@ -425,6 +433,7 @@ export function DebugBar() { value={heapv()} bad={bad(heap(), 0.8)} dim={state.heap.used === undefined} + wide /> </div> </aside> From 4dce485854df0719800c51a1182270ee21c075f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Benoit?= <jerome.benoit@piment-noir.org> Date: Tue, 10 Mar 2026 16:05:45 +0100 Subject: [PATCH 047/200] fix(opencode): add thinking variants support for SAP AI provider (#14958) Co-authored-by: Test <test@test.com> Co-authored-by: Stephen Collings <stevoland@gmail.com> --- packages/opencode/src/provider/transform.ts | 35 ++++- .../opencode/test/provider/transform.test.ts | 141 ++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 471da03cbc..407c268782 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -657,9 +657,21 @@ export namespace ProviderTransform { // https://v5.ai-sdk.dev/providers/ai-sdk-providers/perplexity return {} - case "@mymediset/sap-ai-provider": case "@jerome-benoit/sap-ai-provider-v2": if (model.api.id.includes("anthropic")) { + if (isAnthropicAdaptive) { + return Object.fromEntries( + adaptiveEfforts.map((effort) => [ + effort, + { + thinking: { + type: "adaptive", + }, + effort, + }, + ]), + ) + } return { high: { thinking: { @@ -675,7 +687,26 @@ export namespace ProviderTransform { }, } } - return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }])) + if (model.api.id.includes("gemini") && id.includes("2.5")) { + return { + high: { + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 16000, + }, + }, + max: { + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 24576, + }, + }, + } + } + if (model.api.id.includes("gpt") || /\bo[1-9]/.test(model.api.id)) { + return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }])) + } + return {} } return {} } diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 512819a657..b6d3afc2dc 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -2479,4 +2479,145 @@ describe("ProviderTransform.variants", () => { expect(result).toEqual({}) }) }) + + describe("@jerome-benoit/sap-ai-provider-v2", () => { + test("anthropic models return thinking variants", () => { + const model = createMockModel({ + id: "sap-ai-core/anthropic--claude-sonnet-4", + providerID: "sap-ai-core", + api: { + id: "anthropic--claude-sonnet-4", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["high", "max"]) + expect(result.high).toEqual({ + thinking: { + type: "enabled", + budgetTokens: 16000, + }, + }) + expect(result.max).toEqual({ + thinking: { + type: "enabled", + budgetTokens: 31999, + }, + }) + }) + + test("anthropic 4.6 models return adaptive thinking variants", () => { + const model = createMockModel({ + id: "sap-ai-core/anthropic--claude-sonnet-4-6", + providerID: "sap-ai-core", + api: { + id: "anthropic--claude-sonnet-4-6", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"]) + expect(result.low).toEqual({ + thinking: { + type: "adaptive", + }, + effort: "low", + }) + expect(result.max).toEqual({ + thinking: { + type: "adaptive", + }, + effort: "max", + }) + }) + + test("gemini 2.5 models return thinkingConfig variants", () => { + const model = createMockModel({ + id: "sap-ai-core/gcp--gemini-2.5-pro", + providerID: "sap-ai-core", + api: { + id: "gcp--gemini-2.5-pro", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["high", "max"]) + expect(result.high).toEqual({ + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 16000, + }, + }) + expect(result.max).toEqual({ + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 24576, + }, + }) + }) + + test("gpt models return reasoningEffort variants", () => { + const model = createMockModel({ + id: "sap-ai-core/azure-openai--gpt-4o", + providerID: "sap-ai-core", + api: { + id: "azure-openai--gpt-4o", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + expect(result.low).toEqual({ reasoningEffort: "low" }) + expect(result.high).toEqual({ reasoningEffort: "high" }) + }) + + test("o-series models return reasoningEffort variants", () => { + const model = createMockModel({ + id: "sap-ai-core/azure-openai--o3-mini", + providerID: "sap-ai-core", + api: { + id: "azure-openai--o3-mini", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + expect(result.low).toEqual({ reasoningEffort: "low" }) + expect(result.high).toEqual({ reasoningEffort: "high" }) + }) + + + test("sonar models return empty object", () => { + const model = createMockModel({ + id: "sap-ai-core/perplexity--sonar-pro", + providerID: "sap-ai-core", + api: { + id: "perplexity--sonar-pro", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(result).toEqual({}) + }) + + test("mistral models return empty object", () => { + const model = createMockModel({ + id: "sap-ai-core/mistral--mistral-large", + providerID: "sap-ai-core", + api: { + id: "mistral--mistral-large", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(result).toEqual({}) + }) + }) }) From 5a40158abf02268e4f8fb47870fdc39272f7f697 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:07:35 +0000 Subject: [PATCH 048/200] chore: generate --- packages/opencode/test/provider/transform.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index b6d3afc2dc..9dde1a7131 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -2591,7 +2591,6 @@ describe("ProviderTransform.variants", () => { expect(result.high).toEqual({ reasoningEffort: "high" }) }) - test("sonar models return empty object", () => { const model = createMockModel({ id: "sap-ai-core/perplexity--sonar-pro", From 4c4aed5a875e44aec2856c117fa05e861fa62bb5 Mon Sep 17 00:00:00 2001 From: James Long <longster@gmail.com> Date: Tue, 10 Mar 2026 11:11:28 -0400 Subject: [PATCH 049/200] fix(core): make worktrees read the project id from local workspace (#16795) --- packages/opencode/src/project/project.ts | 63 ++++++++++++++---------- packages/opencode/src/worktree/index.ts | 2 +- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index e1fff1a147..68cece0a52 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -88,6 +88,12 @@ export namespace Project { } } + function readCachedId(dir: string) { + return Filesystem.readText(path.join(dir, "opencode")) + .then((x) => x.trim()) + .catch(() => undefined) + } + export async function fromDirectory(directory: string) { log.info("fromDirectory", { directory }) @@ -101,19 +107,43 @@ export namespace Project { const gitBinary = which("git") // cached id calculation - let id = await Filesystem.readText(path.join(dotgit, "opencode")) - .then((x) => x.trim()) - .catch(() => undefined) + let id = await readCachedId(dotgit) if (!gitBinary) { return { id: id ?? "global", worktree: sandbox, - sandbox: sandbox, + sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), } } + const worktree = await git(["rev-parse", "--git-common-dir"], { + cwd: sandbox, + }) + .then(async (result) => { + const common = gitpath(sandbox, await result.text()) + // Avoid going to parent of sandbox when git-common-dir is empty. + return common === sandbox ? sandbox : path.dirname(common) + }) + .catch(() => undefined) + + if (!worktree) { + return { + id: id ?? "global", + worktree: sandbox, + sandbox, + vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), + } + } + + // In the case of a git worktree, it can't cache the id + // because `.git` is not a folder, but it always needs the + // same project id as the common dir, so we resolve it now + if (id == null) { + id = await readCachedId(path.join(worktree, ".git")) + } + // generate id from root commit if (!id) { const roots = await git(["rev-list", "--max-parents=0", "--all"], { @@ -132,7 +162,7 @@ export namespace Project { return { id: "global", worktree: sandbox, - sandbox: sandbox, + sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), } } @@ -147,7 +177,7 @@ export namespace Project { return { id: "global", worktree: sandbox, - sandbox: sandbox, + sandbox, vcs: "git", } } @@ -161,33 +191,14 @@ export namespace Project { if (!top) { return { id, - sandbox, worktree: sandbox, + sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), } } sandbox = top - const worktree = await git(["rev-parse", "--git-common-dir"], { - cwd: sandbox, - }) - .then(async (result) => { - const common = gitpath(sandbox, await result.text()) - // Avoid going to parent of sandbox when git-common-dir is empty. - return common === sandbox ? sandbox : path.dirname(common) - }) - .catch(() => undefined) - - if (!worktree) { - return { - id, - sandbox, - worktree: sandbox, - vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), - } - } - return { id, sandbox, diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 8b5accc333..aa5613010c 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -413,7 +413,7 @@ export namespace Worktree { await runStartScripts(info.directory, { projectID, extra }) } - void start().catch((error) => { + return start().catch((error) => { log.error("worktree start task failed", { directory: info.directory, error }) }) } From 69ddc91c35ef04e173cde6f8627773c1116fcd39 Mon Sep 17 00:00:00 2001 From: James Long <longster@gmail.com> Date: Tue, 10 Mar 2026 11:12:14 -0400 Subject: [PATCH 050/200] fix(core): a chunk timeout when processing llm stream (#16366) --- packages/opencode/src/config/config.ts | 8 ++ packages/opencode/src/provider/provider.ts | 73 ++++++++++++++++--- .../opencode/test/provider/provider.test.ts | 2 + packages/web/src/content/docs/config.mdx | 4 +- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 6b4242a225..ef2821727b 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -972,6 +972,14 @@ export namespace Config { .describe( "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", ), + chunkTimeout: z + .number() + .int() + .positive() + .optional() + .describe( + "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.", + ), }) .catchall(z.any()) .optional(), diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 09035d272c..c174ebd9ff 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -46,6 +46,8 @@ import { GoogleAuth } from "google-auth-library" import { ProviderTransform } from "./transform" import { Installation } from "../installation" +const DEFAULT_CHUNK_TIMEOUT = 120_000 + export namespace Provider { const log = Log.create({ service: "provider" }) @@ -85,6 +87,54 @@ export namespace Provider { }) } + function wrapSSE(res: Response, ms: number, ctl: AbortController) { + if (typeof ms !== "number" || ms <= 0) return res + if (!res.body) return res + if (!res.headers.get("content-type")?.includes("text/event-stream")) return res + + const reader = res.body.getReader() + const body = new ReadableStream<Uint8Array>({ + async pull(ctrl) { + const part = await new Promise<Awaited<ReturnType<typeof reader.read>>>((resolve, reject) => { + const id = setTimeout(() => { + const err = new Error("SSE read timed out") + ctl.abort(err) + void reader.cancel(err) + reject(err) + }, ms) + + reader.read().then( + (part) => { + clearTimeout(id) + resolve(part) + }, + (err) => { + clearTimeout(id) + reject(err) + }, + ) + }) + + if (part.done) { + ctrl.close() + return + } + + ctrl.enqueue(part.value) + }, + async cancel(reason) { + ctl.abort(reason) + await reader.cancel(reason) + }, + }) + + return new Response(body, { + headers: new Headers(res.headers), + status: res.status, + statusText: res.statusText, + }) + } + const BUNDLED_PROVIDERS: Record<string, (options: any) => SDK> = { "@ai-sdk/amazon-bedrock": createAmazonBedrock, "@ai-sdk/anthropic": createAnthropic, @@ -1092,21 +1142,23 @@ export namespace Provider { if (existing) return existing const customFetch = options["fetch"] + const chunkTimeout = options["chunkTimeout"] || DEFAULT_CHUNK_TIMEOUT + delete options["chunkTimeout"] options["fetch"] = async (input: any, init?: BunFetchRequestInit) => { // Preserve custom fetch if it exists, wrap it with timeout logic const fetchFn = customFetch ?? fetch const opts = init ?? {} + const chunkAbortCtl = typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined + const signals: AbortSignal[] = [] - if (options["timeout"] !== undefined && options["timeout"] !== null) { - const signals: AbortSignal[] = [] - if (opts.signal) signals.push(opts.signal) - if (options["timeout"] !== false) signals.push(AbortSignal.timeout(options["timeout"])) + if (opts.signal) signals.push(opts.signal) + if (chunkAbortCtl) signals.push(chunkAbortCtl.signal) + if (options["timeout"] !== undefined && options["timeout"] !== null && options["timeout"] !== false) + signals.push(AbortSignal.timeout(options["timeout"])) - const combined = signals.length > 1 ? AbortSignal.any(signals) : signals[0] - - opts.signal = combined - } + const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals) + if (combined) opts.signal = combined // Strip openai itemId metadata following what codex does // Codex uses #[serde(skip_serializing)] on id fields for all item types: @@ -1126,11 +1178,14 @@ export namespace Provider { } } - return fetchFn(input, { + const res = await fetchFn(input, { ...opts, // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682 timeout: false, }) + + if (!chunkAbortCtl) return res + return wrapSSE(res, chunkTimeout, chunkAbortCtl) } const bundledFn = BUNDLED_PROVIDERS[model.api.npm] diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 11c943db6f..4c6eaf8b22 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -260,6 +260,7 @@ test("env variable takes precedence, config merges options", async () => { anthropic: { options: { timeout: 60000, + chunkTimeout: 15000, }, }, }, @@ -277,6 +278,7 @@ test("env variable takes precedence, config merges options", async () => { expect(providers["anthropic"]).toBeDefined() // Config options should be merged expect(providers["anthropic"].options.timeout).toBe(60000) + expect(providers["anthropic"].options.chunkTimeout).toBe(15000) }, }) }) diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index 038f253274..d2770ee209 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -244,7 +244,7 @@ You can configure the providers and models you want to use in your OpenCode conf The `small_model` option configures a separate model for lightweight tasks like title generation. By default, OpenCode tries to use a cheaper model if one is available from your provider, otherwise it falls back to your main model. -Provider options can include `timeout` and `setCacheKey`: +Provider options can include `timeout`, `chunkTimeout`, and `setCacheKey`: ```json title="opencode.json" { @@ -253,6 +253,7 @@ Provider options can include `timeout` and `setCacheKey`: "anthropic": { "options": { "timeout": 600000, + "chunkTimeout": 30000, "setCacheKey": true } } @@ -261,6 +262,7 @@ Provider options can include `timeout` and `setCacheKey`: ``` - `timeout` - Request timeout in milliseconds (default: 300000). Set to `false` to disable. +- `chunkTimeout` - Timeout in milliseconds between streamed response chunks. If no chunk arrives in time, the request is aborted. - `setCacheKey` - Ensure a cache key is always set for designated provider. You can also configure [local models](/docs/models#local). [Learn more](/docs/models). From a4330a225dda8aa088423ce3be88f7fad30fb862 Mon Sep 17 00:00:00 2001 From: James Long <longster@gmail.com> Date: Tue, 10 Mar 2026 11:12:51 -0400 Subject: [PATCH 051/200] feat(core): allow passing `workspaceID` into session create endpoint (#16798) --- packages/opencode/src/cli/cmd/tui/app.tsx | 3 +++ .../src/cli/cmd/tui/component/dialog-workspace-list.tsx | 2 +- .../opencode/src/cli/cmd/tui/component/prompt/index.tsx | 5 ++++- packages/opencode/src/cli/cmd/tui/context/route.tsx | 1 + packages/opencode/src/cli/cmd/tui/routes/home.tsx | 1 + packages/opencode/src/session/index.ts | 6 +++++- packages/sdk/js/src/v2/gen/sdk.gen.ts | 2 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 1 + 8 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index d5aef34f6e..4f7c94b1d3 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -402,9 +402,12 @@ function App() { const current = promptRef.current // Don't require focus - if there's any text, preserve it const currentPrompt = current?.current?.input ? current.current : undefined + const workspaceID = + route.data.type === "session" ? sync.session.get(route.data.sessionID)?.workspaceID : undefined route.navigate({ type: "home", initialPrompt: currentPrompt, + workspaceID, }) dialog.clear() }, diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx index a25b20505c..b11ad6a734 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx @@ -47,7 +47,7 @@ async function openWorkspace(input: { } let created: Session | undefined while (!created) { - const result = await client.session.create({}).catch(() => undefined) + const result = await client.session.create({ workspaceID: input.workspaceID }).catch(() => undefined) if (!result) { input.toast.show({ message: "Failed to open workspace", diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 77577b2a0e..2d99051fb9 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -37,6 +37,7 @@ import { DialogSkill } from "../dialog-skill" export type PromptProps = { sessionID?: string + workspaceID?: string visible?: boolean disabled?: boolean onSubmit?: () => void @@ -542,7 +543,9 @@ export function Prompt(props: PromptProps) { let sessionID = props.sessionID if (sessionID == null) { - const res = await sdk.client.session.create({}) + const res = await sdk.client.session.create({ + workspaceID: props.workspaceID, + }) if (res.error) { console.log("Creating a session failed:", res.error) diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index 358461921b..e96cd2c3a4 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -5,6 +5,7 @@ import type { PromptInfo } from "../component/prompt/history" export type HomeRoute = { type: "home" initialPrompt?: PromptInfo + workspaceID?: string } export type SessionRoute = { diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 24ea8f3b31..e76e165b26 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -121,6 +121,7 @@ export function Home() { promptRef.set(r) }} hint={Hint} + workspaceID={route.workspaceID} /> </box> <box height={4} minHeight={0} width="100%" maxWidth={75} alignItems="center" paddingTop={3} flexShrink={1}> diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index b117632051..5cc4d7da8d 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -219,6 +219,7 @@ export namespace Session { parentID: Identifier.schema("session").optional(), title: z.string().optional(), permission: Info.shape.permission, + workspaceID: Identifier.schema("workspace").optional(), }) .optional(), async (input) => { @@ -227,6 +228,7 @@ export namespace Session { directory: Instance.directory, title: input?.title, permission: input?.permission, + workspaceID: input?.workspaceID, }) }, ) @@ -242,6 +244,7 @@ export namespace Session { const title = getForkedTitle(original.title) const session = await createNext({ directory: Instance.directory, + workspaceID: original.workspaceID, title, }) const msgs = await messages({ sessionID: input.sessionID }) @@ -292,6 +295,7 @@ export namespace Session { id?: string title?: string parentID?: string + workspaceID?: string directory: string permission?: PermissionNext.Ruleset }) { @@ -301,7 +305,7 @@ export namespace Session { version: Installation.VERSION, projectID: Instance.project.id, directory: input.directory, - workspaceID: WorkspaceContext.workspaceID, + workspaceID: input.workspaceID, parentID: input.parentID, title: input.title ?? createDefaultTitle(!!input.parentID), permission: input.permission, diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 22dcfec355..2bb2edcd17 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -1295,6 +1295,7 @@ export class Session2 extends HeyApiClient { parentID?: string title?: string permission?: PermissionRuleset + workspaceID?: string }, options?: Options<never, ThrowOnError>, ) { @@ -1308,6 +1309,7 @@ export class Session2 extends HeyApiClient { { in: "body", key: "parentID" }, { in: "body", key: "title" }, { in: "body", key: "permission" }, + { in: "body", key: "workspaceID" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 71e075b391..e7ef9ae49c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2764,6 +2764,7 @@ export type SessionCreateData = { parentID?: string title?: string permission?: PermissionRuleset + workspaceID?: string } path?: never query?: { From 54ba59d3e17c7734f86a967bf52a5aefd76f0eba Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:14:46 +0000 Subject: [PATCH 052/200] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 6 +++++- packages/sdk/openapi.json | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index e7ef9ae49c..a47b18db21 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1225,7 +1225,11 @@ export type ProviderConfig = { * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout. */ timeout?: number | false - [key: string]: unknown | string | boolean | number | false | undefined + /** + * Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted. + */ + chunkTimeout?: number + [key: string]: unknown | string | boolean | number | false | number | undefined } } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index d1198c11dd..c3e54a7a11 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1832,6 +1832,10 @@ }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" + }, + "workspaceID": { + "type": "string", + "pattern": "^wrk.*" } } } @@ -10108,6 +10112,12 @@ "const": false } ] + }, + "chunkTimeout": { + "description": "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 } }, "additionalProperties": {} From ad08fd57df450d4371eddfdf8a693ff378c2259c Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:27:04 -0500 Subject: [PATCH 053/200] chore: rekram1-node is no longer on vacation (#16905) --- .opencode/tool/github-triage.ts | 20 +++++++------------- .opencode/tool/github-triage.txt | 2 -- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts index 8ad0212ad0..ed80f49d54 100644 --- a/.opencode/tool/github-triage.ts +++ b/.opencode/tool/github-triage.ts @@ -5,16 +5,8 @@ import DESCRIPTION from "./github-triage.txt" const TEAM = { desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"], zen: ["fwang", "MrMushrooooom"], - tui: [ - "thdxr", - "kommander", - // "rekram1-node" (on vacation) - ], - core: [ - "thdxr", - // "rekram1-node", (on vacation) - "jlongster", - ], + tui: ["thdxr", "kommander", "rekram1-node"], + core: ["thdxr", "rekram1-node", "jlongster"], docs: ["R44VC0RP"], windows: ["Hona"], } as const @@ -50,7 +42,10 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) { export default tool({ description: DESCRIPTION, args: { - assignee: tool.schema.enum(ASSIGNEES as [string, ...string[]]).describe("The username of the assignee"), + assignee: tool.schema + .enum(ASSIGNEES as [string, ...string[]]) + .describe("The username of the assignee") + .default("rekram1-node"), labels: tool.schema .array(tool.schema.enum(["nix", "opentui", "perf", "web", "desktop", "zen", "docs", "windows", "core"])) .describe("The labels(s) to add to the issue") @@ -73,8 +68,7 @@ export default tool({ results.push("Dropped label: nix (issue does not mention nix)") } - // const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee - const assignee = web ? pick(TEAM.desktop) : args.assignee + const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee if (labels.includes("zen") && !zen) { throw new Error("Only add the zen label when issue title/body contains 'zen'") diff --git a/.opencode/tool/github-triage.txt b/.opencode/tool/github-triage.txt index 1a2d69bdb5..4369ed2351 100644 --- a/.opencode/tool/github-triage.txt +++ b/.opencode/tool/github-triage.txt @@ -4,5 +4,3 @@ Choose labels and assignee using the current triage policy and ownership rules. Pick the most fitting labels for the issue and assign one owner. If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random. - -(Note: rekram1-node is on vacation, do not assign issues to him.) From 9c4325bcf8070d84a6911ae78b898c116ebad2ac Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:32:05 -0500 Subject: [PATCH 054/200] fix(core): don't permit access to system directories (#16891) --- packages/opencode/src/file/index.ts | 6 +-- packages/opencode/src/file/protected.ts | 59 +++++++++++++++++++++++++ packages/opencode/src/file/watcher.ts | 3 +- 3 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 packages/opencode/src/file/protected.ts diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 7a51ca36c7..e03fc8a9f3 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -11,6 +11,7 @@ import { Ripgrep } from "./ripgrep" import fuzzysort from "fuzzysort" import { Global } from "../global" import { git } from "@/util/git" +import { Protected } from "./protected" export namespace File { const log = Log.create({ service: "file" }) @@ -345,10 +346,7 @@ export namespace File { if (isGlobalHome) { const dirs = new Set<string>() - const ignore = new Set<string>() - - if (process.platform === "darwin") ignore.add("Library") - if (process.platform === "win32") ignore.add("AppData") + const ignore = Protected.names() const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"]) const shouldIgnore = (name: string) => name.startsWith(".") || ignore.has(name) diff --git a/packages/opencode/src/file/protected.ts b/packages/opencode/src/file/protected.ts new file mode 100644 index 0000000000..d519746193 --- /dev/null +++ b/packages/opencode/src/file/protected.ts @@ -0,0 +1,59 @@ +import path from "path" +import os from "os" + +const home = os.homedir() + +// macOS directories that trigger TCC (Transparency, Consent, and Control) +// permission prompts when accessed by a non-sandboxed process. +const DARWIN_HOME = [ + // Media + "Music", + "Pictures", + "Movies", + // User-managed folders synced via iCloud / subject to TCC + "Downloads", + "Desktop", + "Documents", + // Other system-managed + "Public", + "Applications", + "Library", +] + +const DARWIN_LIBRARY = [ + "Application Support/AddressBook", + "Calendars", + "Mail", + "Messages", + "Safari", + "Cookies", + "Application Support/com.apple.TCC", + "PersonalizationPortrait", + "Metadata/CoreSpotlight", + "Suggestions", +] + +const DARWIN_ROOT = ["/.DocumentRevisions-V100", "/.Spotlight-V100", "/.Trashes", "/.fseventsd"] + +const WIN32_HOME = ["AppData", "Downloads", "Desktop", "Documents", "Pictures", "Music", "Videos", "OneDrive"] + +export namespace Protected { + /** Directory basenames to skip when scanning the home directory. */ + export function names(): ReadonlySet<string> { + if (process.platform === "darwin") return new Set(DARWIN_HOME) + if (process.platform === "win32") return new Set(WIN32_HOME) + return new Set() + } + + /** Absolute paths that should never be watched, stated, or scanned. */ + export function paths(): string[] { + if (process.platform === "darwin") + return [ + ...DARWIN_HOME.map((n) => path.join(home, n)), + ...DARWIN_LIBRARY.map((n) => path.join(home, "Library", n)), + ...DARWIN_ROOT, + ] + if (process.platform === "win32") return WIN32_HOME.map((n) => path.join(home, n)) + return [] + } +} diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 537f526463..3797c16270 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -14,6 +14,7 @@ import type ParcelWatcher from "@parcel/watcher" import { Flag } from "@/flag/flag" import { readdir } from "fs/promises" import { git } from "@/util/git" +import { Protected } from "./protected" const SUBSCRIBE_TIMEOUT_MS = 10_000 @@ -76,7 +77,7 @@ export namespace FileWatcher { if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) { const pending = w.subscribe(Instance.directory, subscribe, { - ignore: [...FileIgnore.PATTERNS, ...cfgIgnores], + ignore: [...FileIgnore.PATTERNS, ...cfgIgnores, ...Protected.paths()], backend, }) const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => { From 613562f5047aa0bad934da401146a04ed8adce84 Mon Sep 17 00:00:00 2001 From: Dax <mail@thdxr.com> Date: Tue, 10 Mar 2026 12:53:47 -0400 Subject: [PATCH 055/200] core: make account login upgrades safe while adding multi-account workspace auth (#15487) Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> --- AGENTS.md | 4 + bun.lock | 1393 ++++++++--------- package.json | 1 + .../20260228203230_blue_harpoon/migration.sql | 17 + .../20260228203230_blue_harpoon/snapshot.json | 1102 +++++++++++++ .../migration.sql | 3 + .../snapshot.json | 1215 ++++++++++++++ packages/opencode/package.json | 2 + packages/opencode/src/account/account.sql.ts | 35 + packages/opencode/src/account/index.ts | 43 + packages/opencode/src/account/repo.ts | 138 ++ packages/opencode/src/account/schema.ts | 73 + packages/opencode/src/account/service.ts | 384 +++++ packages/opencode/src/cli/cmd/account.ts | 196 +++ packages/opencode/src/cli/cmd/import.ts | 27 +- .../src/cli/cmd/{auth.ts => providers.ts} | 44 +- .../cli/cmd/tui/component/dialog-provider.tsx | 2 +- .../src/cli/cmd/tui/routes/session/index.tsx | 14 +- packages/opencode/src/cli/effect/prompt.ts | 25 + packages/opencode/src/config/config.ts | 33 +- .../src/control-plane/workspace.sql.ts | 2 +- packages/opencode/src/control/control.sql.ts | 22 - packages/opencode/src/control/index.ts | 67 - packages/opencode/src/effect/runtime.ts | 4 + packages/opencode/src/index.ts | 9 +- packages/opencode/src/project/project.sql.ts | 2 +- packages/opencode/src/session/session.sql.ts | 6 +- packages/opencode/src/share/share-next.ts | 88 +- packages/opencode/src/share/share.sql.ts | 2 +- packages/opencode/src/storage/db.ts | 6 +- packages/opencode/src/storage/schema.ts | 4 +- .../opencode/src/util/effect-http-client.ts | 11 + packages/opencode/src/util/schema.ts | 17 + packages/opencode/test/account/repo.test.ts | 338 ++++ .../opencode/test/account/service.test.ts | 223 +++ packages/opencode/test/cli/import.test.ts | 20 +- .../test/cli/plugin-auth-picker.test.ts | 2 +- packages/opencode/test/config/config.test.ts | 47 + packages/opencode/test/fixture/effect.ts | 7 + .../opencode/test/share/share-next.test.ts | 76 + packages/opencode/tsconfig.json | 7 +- 41 files changed, 4793 insertions(+), 918 deletions(-) create mode 100644 packages/opencode/migration/20260228203230_blue_harpoon/migration.sql create mode 100644 packages/opencode/migration/20260228203230_blue_harpoon/snapshot.json create mode 100644 packages/opencode/migration/20260309230000_move_org_to_state/migration.sql create mode 100644 packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json create mode 100644 packages/opencode/src/account/account.sql.ts create mode 100644 packages/opencode/src/account/index.ts create mode 100644 packages/opencode/src/account/repo.ts create mode 100644 packages/opencode/src/account/schema.ts create mode 100644 packages/opencode/src/account/service.ts create mode 100644 packages/opencode/src/cli/cmd/account.ts rename packages/opencode/src/cli/cmd/{auth.ts => providers.ts} (92%) create mode 100644 packages/opencode/src/cli/effect/prompt.ts delete mode 100644 packages/opencode/src/control/control.sql.ts delete mode 100644 packages/opencode/src/control/index.ts create mode 100644 packages/opencode/src/effect/runtime.ts create mode 100644 packages/opencode/src/util/effect-http-client.ts create mode 100644 packages/opencode/src/util/schema.ts create mode 100644 packages/opencode/test/account/repo.test.ts create mode 100644 packages/opencode/test/account/service.test.ts create mode 100644 packages/opencode/test/fixture/effect.ts create mode 100644 packages/opencode/test/share/share-next.test.ts diff --git a/AGENTS.md b/AGENTS.md index 2158d73af1..0b080ac4e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -122,3 +122,7 @@ const table = sqliteTable("session", { - Avoid mocks as much as possible - Test actual implementation, do not duplicate logic into tests - Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`. + +## Type Checking + +- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly. diff --git a/bun.lock b/bun.lock index bb85a46e01..1721ba330b 100644 --- a/bun.lock +++ b/bun.lock @@ -352,6 +352,7 @@ "decimal.js": "10.5.0", "diff": "catalog:", "drizzle-orm": "1.0.0-beta.16-ea816b6", + "effect": "catalog:", "fuzzysort": "3.1.0", "glob": "13.0.5", "google-auth-library": "10.5.0", @@ -382,6 +383,7 @@ }, "devDependencies": { "@babel/core": "7.28.4", + "@effect/language-service": "0.79.0", "@octokit/webhooks-types": "7.6.1", "@opencode-ai/script": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", @@ -609,6 +611,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.16-ea816b6", "drizzle-orm": "1.0.0-beta.16-ea816b6", + "effect": "4.0.0-beta.29", "fuzzysort": "3.1.0", "hono": "4.10.7", "hono-openapi": "1.1.2", @@ -656,15 +659,15 @@ "@ai-sdk/cohere": ["@ai-sdk/cohere@2.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJ9kP5cEDJwo8qpITq5TQFD8YNfNtW+HbyvWwrKMbFzmiMvIZuk95HIaFXE7PCTuZsqMA05yYu+qX/vQ3rNKjA=="], - "@ai-sdk/deepgram": ["@ai-sdk/deepgram@1.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yQ5izccuO7+WDtitbJsqH7qX7BqVVonUbPZBxQypF3zqBXbCI3/3CH+0XbsWRVRWFN8/rmCAbgHg8DXjaqVQsw=="], + "@ai-sdk/deepgram": ["@ai-sdk/deepgram@1.0.24", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-E+wzGPSa/XHmajO3WtX8mtq0ewy04tsHSpU6/SGwqbiykwWba/emi7ayZ4ir89s5OzbAen2g7T9zZiEchMfkHQ=="], "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@1.0.36", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.33", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LndvRktEgY2IFu4peDJMEXcjhHEEFtM0upLx/J64kCpFHCifalXpK4PPSX3PVndnn0bJzvamO5+fc0z2ooqBZw=="], - "@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-H+5UGOGGZB5tYpX+3fcWxoPPDzRTEH1w6z+yD7053PmKZfHcxSJWv9HwLEyEkAv3ef1E7MIyG5EB+HmkclQ+KQ=="], + "@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.35", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Qvh2yxL5zJS9RO/Bf12pyYBIDmn+9GR1hT6e28IYWQWnt2Xq0h9XGps6XagLAv3VYYFg8c/ozkWVd4kXLZ25HA=="], - "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@1.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B9uz4+KEB5RkphL9d9XL03iA24g3f0VAeklNlq7StY7L8Mo2sBx3Bg8Udzv7G3xJmT41GuzR5pR0FkKUTju0Rg=="], + "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@1.0.24", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ee2At5jgV+SqC6nrtPq20iH7N/aN+O36LrA4gkzVM4cmhM7bvQKVkOXhC1XxG+wsYG6UZi3Nekoi8MEjNWuRrw=="], - "@ai-sdk/fireworks": ["@ai-sdk/fireworks@1.0.34", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.33", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-JvRdp8bMokbmp/mFz0qHPAhvAZT+vR+c9o4lTkENkDcbRBcNYUN05sSWCuwiVDdz9T+8GW7goAec6fXJBzjIFw=="], + "@ai-sdk/fireworks": ["@ai-sdk/fireworks@1.0.35", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.34", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-inUq29XvSVDer6JIeOkwAmCFxOtHPU0OZEhwaWoe3PI59naHIW4RIFA9wppLLV5fJI9WQcAfDKy0ZHW9nV3UJw=="], "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5Nrkj8B4MzkkOfjjA+Cs5pamkbkK4lI11bx80QV7TFcen/hWA8wEC+UVzwuM5H2zpekoNMjvl6GonHnR62XIZw=="], @@ -746,29 +749,29 @@ "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.933.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-bucket-endpoint": "3.930.0", "@aws-sdk/middleware-expect-continue": "3.930.0", "@aws-sdk/middleware-flexible-checksums": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-location-constraint": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-sdk-s3": "3.932.0", "@aws-sdk/middleware-ssec": "3.930.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/signature-v4-multi-region": "3.932.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-KxwZvdxdCeWK6o8mpnb+kk7Kgb8V+8AjTwSXUWH1UAD85B0tjdo1cSfE5zoR5fWGol4Ml5RLez12a6LPhsoTqA=="], - "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VLUN+wIeNX24fg12SCbzTUBnBENlL014yMKZvRhPkcn4wHR6LKgNrjsG3fZ03Xs0XoKaGtNFi1VVrq666sGBoQ=="], + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zwGLSiK48z3PzKpQiDMKP85+fpIrPMF1qQOQW9OW7BGj5AuBZIisT2O4VzIgYJeh+t47MLU7VgBQL7muc+MJDg=="], "@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/credential-provider-node": "3.782.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-Q1QLY3xE2z1trgriusP/6w40mI/yJjM524bN4gs+g6YX4sZGufpa7+Dj+JjL4fz8f9BCJ3ZlI+p4WxFxH7qvdQ=="], "@aws-sdk/core": ["@aws-sdk/core@3.932.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-AS8gypYQCbNojwgjvZGkJocC2CoEICDx9ZJ15ILsv+MlcCVLtUJSRSx3VzJOUY2EEIaGLRrPNlIqyn/9/fySvA=="], - "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.3", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.980.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-dW/DqTk90XW7hIngqntAVtJJyrkS51wcLhGz39lOMe0TlSmZl+5R/UGnAZqNbXmWuJHLzxe+MLgagxH41aTsAQ=="], + "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.11", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.996.8", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-yHBjinYauxSvikf15EtgXyZ9TBIMVHUSWFPycQtPltTINpK+uv6K22zKkVsbxpB0gvsdRdIWP0UG5gejM+jPuQ=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ZptrOwQynfupubvcngLkbdIq/aXvl/czdpEG8XJ8mN8Nb19BR0jaK0bR+tfuMU36Ez9q4xv7GGkHFqEEP2hUUQ=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MBAMW6YELzE1SdkOniqr51mrjapQUv8JXSGxtwRjQV0mwVDutVsn22OPAUt4RcLRvdiHQmNBDEFP9iTeSVCOlA=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-hECWoOoH386bGr89NQc9vA/abkGf5TJrMREt+lhNcnSNmoBS04fK7vc3LrJBSQAUGGVj0Tz3f4dHB3w5veovig=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-9EJROO8LXll5a7eUFqu48k6BChrtokbmgeMWmsH7lBb6lVbtjslUYz/ShLi+SHkYzTomiGBhmzTW7y+H4BxsnA=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-zr1csEu9n4eDiHMTYJabX1mDGuGLgjgUnNckIivvk43DocJC9/f6DefFrnUPZXE+GHtbW50YuXb+JIxKykU74A=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/credential-provider-env": "^3.972.17", "@aws-sdk/credential-provider-http": "^3.972.19", "@aws-sdk/credential-provider-login": "^3.972.18", "@aws-sdk/credential-provider-process": "^3.972.17", "@aws-sdk/credential-provider-sso": "^3.972.18", "@aws-sdk/credential-provider-web-identity": "^3.972.18", "@aws-sdk/nested-clients": "^3.996.8", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-vthIAXJISZnj2576HeyLBj4WTeX+I7PwWeRkbOa0mVX39K13SCGxCgOFuKj2ytm9qTlLOmXe4cdEnroteFtJfw=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-m4RIpVgZChv0vWS/HKChg1xLgZPpx8Z+ly9Fv7FwA8SOfuC6I3htcSaBz2Ch4bneRIiBUhwP4ziUo0UZgtJStQ=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.8", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-kINzc5BBxdYBkPZ0/i1AMPMOk5b5QaFNbYMElVw5QTX13AKj6jcxnv/YNl9oW9mg+Y08ti19hh01HhyEAxsSJQ=="], "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.933.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.932.0", "@aws-sdk/credential-provider-http": "3.932.0", "@aws-sdk/credential-provider-ini": "3.933.0", "@aws-sdk/credential-provider-process": "3.932.0", "@aws-sdk/credential-provider-sso": "3.933.0", "@aws-sdk/credential-provider-web-identity": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-L2dE0Y7iMLammQewPKNeEh1z/fdJyYEU+/QsLBD9VEh+SXcN/FIyTi21Isw8wPZN6lMB9PDVtISzBnF8HuSFrw=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-gOWl0Fe2gETj5Bk151+LYKpeGi2lBDLNu+NMNpHRlIrKHdBmVun8/AalwMK8ci4uRfG5a3/+zvZBMpuen1SZ0A=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.17", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-c8G8wT1axpJDgaP3xzcy+q8Y1fTi9A2eIQJvyhQ9xuXrUZhlCfXbC0vM9bM1CUXiZppFQ1p7g0tuUMvil/gCPg=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.9", "", { "dependencies": { "@aws-sdk/client-sso": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/token-providers": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ey7S686foGTArvFhi3ifQXmgptKYvLSGE2250BAQceMSXZddz7sUSNERGJT2S7u5KIe/kgugxrt01hntXVln6w=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.8", "@aws-sdk/token-providers": "3.1005.0", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-YHYEfj5S2aqInRt5ub8nDOX8vAxgMvd84wm2Y3WVNfFa/53vOv9T7WOAqXI25qjj3uEcV46xxfqdDQk04h5XQA=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8LnfS76nHXoEc9aRRiMMpxZxJeDG0yusdyo3NvPhCgESmBUgpMa4luhGbClW5NoX/qRcGxxM6Z/esqANSNMTow=="], + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.18", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.8", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-OqlEQpJ+J3T5B96qtC1zLLwkBloechP+fezKbCH0sbd2cCc0Ra55XpxWpk/hRj69xAOYtHvoC4orx6eTa4zU7g=="], "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.993.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-1M/nukgPSLqe9krzOKHnE8OylUaKAiokAV3xRLdeExVHcRE7WG5uzCTKWTj1imKvPjDqXq/FWhlbbdWIn7xIwA=="], @@ -798,7 +801,7 @@ "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.932.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-NCIRJvoRc9246RZHIusY1+n/neeG2yGhBGdKhghmrNdM+mLLN6Ii7CKFZjx3DhxtpHMpl1HWLTMhdVrGwP2upw=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.993.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-+35g4c+8r7sB9Sjp1KPdM8qxGn6B/shBjJtEUN4e+Edw9UEQlZKIzioOGu3UAbyE0a/s450LdLZr4wbJChtmww=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1005.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.8", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw=="], "@aws-sdk/types": ["@aws-sdk/types@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-we/vaAgwlEFW7IeftmCLlLMw+6hFs3DzZPJw7lVHbj/5HJ0bz9gndxEsS2lQoeJ1zhiiLqAqvXxmM43s0MBg0A=="], @@ -806,7 +809,7 @@ "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-M2oEKBzzNAYr136RRc6uqw3aWlwCxqTP1Lawps9E1d2abRPvl1p1ztQmmXp1Ak4rv8eByIZ+yQyKQ3zPdRG5dw=="], - "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="], + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-q6lCRm6UAe+e1LguM5E4EqM9brQlDem4XDcQ87NzEvlTW6GzmNCO0w1jS0XgCFXQHjDxjdlNFX+5sRbHijwklg=="], @@ -832,7 +835,7 @@ "@azure/core-paging": ["@azure/core-paging@1.6.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA=="], - "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.22.2", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg=="], + "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="], "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="], @@ -848,11 +851,11 @@ "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], - "@azure/msal-browser": ["@azure/msal-browser@4.28.2", "", { "dependencies": { "@azure/msal-common": "15.14.2" } }, "sha512-6vYUMvs6kJxJgxaCmHn/F8VxjLHNh7i9wzfwPGf8kyBJ8Gg2yvBXx175Uev8LdrD1F5C4o7qHa2CC4IrhGE1XQ=="], + "@azure/msal-browser": ["@azure/msal-browser@4.29.0", "", { "dependencies": { "@azure/msal-common": "15.15.0" } }, "sha512-/f3eHkSNUTl6DLQHm+bKecjBKcRQxbd/XLx8lvSYp8Nl/HRyPuIPOijt9Dt0sH50/SxOwQ62RnFCmFlGK+bR/w=="], - "@azure/msal-common": ["@azure/msal-common@15.14.2", "", {}, "sha512-n8RBJEUmd5QotoqbZfd+eGBkzuFI1KX6jw2b3WcpSyGjwmzoeI/Jb99opIBPHpb8y312NB+B6+FGi2ZVSR8yfA=="], + "@azure/msal-common": ["@azure/msal-common@15.15.0", "", {}, "sha512-/n+bN0AKlVa+AOcETkJSKj38+bvFs78BaP4rNtv3MJCmPH0YrHiskMRe74OhyZ5DZjGISlFyxqvf9/4QVEi2tw=="], - "@azure/msal-node": ["@azure/msal-node@3.8.7", "", { "dependencies": { "@azure/msal-common": "15.14.2", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-a+Xnrae+uwLnlw68bplS1X4kuJ9F/7K6afuMFyRkNIskhjgDezl5Fhrx+1pmAlDmC0VaaAxjRQMp1OmcqVwkIg=="], + "@azure/msal-node": ["@azure/msal-node@3.8.8", "", { "dependencies": { "@azure/msal-common": "15.15.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-+f1VrJH1iI517t4zgmuhqORja0bL6LDQXfBqkjuMmfTYXTQQnh1EvwwxO3UbKLT05N0obF72SRHFrC1RBDv5Gg=="], "@azure/storage-blob": ["@azure/storage-blob@12.31.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.3.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg=="], @@ -964,6 +967,8 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], + "@effect/language-service": ["@effect/language-service@0.79.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DEmIOsg1GjjP6s9HXH1oJrW+gDmzkhVv9WOZl6to5eNyyCrjz1S2PDqQ7aYrW/HuifhfwI5Bik1pK4pj7Z+lrg=="], + "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], @@ -1056,13 +1061,13 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@expressive-code/core": ["@expressive-code/core@0.41.6", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g=="], + "@expressive-code/core": ["@expressive-code/core@0.41.7", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg=="], - "@expressive-code/plugin-frames": ["@expressive-code/plugin-frames@0.41.6", "", { "dependencies": { "@expressive-code/core": "^0.41.6" } }, "sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg=="], + "@expressive-code/plugin-frames": ["@expressive-code/plugin-frames@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7" } }, "sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA=="], - "@expressive-code/plugin-shiki": ["@expressive-code/plugin-shiki@0.41.6", "", { "dependencies": { "@expressive-code/core": "^0.41.6", "shiki": "^3.2.2" } }, "sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng=="], + "@expressive-code/plugin-shiki": ["@expressive-code/plugin-shiki@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7", "shiki": "^3.2.2" } }, "sha512-DL605bLrUOgqTdZ0Ot5MlTaWzppRkzzqzeGEu7ODnHF39IkEBbFdsC7pbl3LbUQ1DFtnfx6rD54k/cdofbW6KQ=="], - "@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.6", "", { "dependencies": { "@expressive-code/core": "^0.41.6" } }, "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q=="], + "@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7" } }, "sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw=="], "@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.5", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A=="], @@ -1080,20 +1085,18 @@ "@fastify/rate-limit": ["@fastify/rate-limit@10.3.0", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "fastify-plugin": "^5.0.0", "toad-cache": "^3.7.0" } }, "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q=="], - "@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="], + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], - "@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="], + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.7", "", { "dependencies": { "@floating-ui/dom": "^1.7.5" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg=="], + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], - "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@fontsource/ibm-plex-mono": ["@fontsource/ibm-plex-mono@5.2.5", "", {}, "sha512-G09N3GfuT9qj3Ax2FDZvKqZttzM3v+cco2l8uXamhKyXLdmlaUDH5o88/C3vtTHj2oT7yRKsvxz9F+BXbWKMYA=="], "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], - "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], - "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="], "@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="], @@ -1110,7 +1113,7 @@ "@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="], - "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], + "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], "@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="], @@ -1158,7 +1161,7 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], - "@internationalized/date": ["@internationalized/date@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q=="], + "@internationalized/date": ["@internationalized/date@3.12.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ=="], "@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="], @@ -1322,6 +1325,18 @@ "@motionone/utils": ["@motionone/utils@10.18.0", "", { "dependencies": { "@motionone/types": "^10.17.1", "hey-listen": "^1.0.8", "tslib": "^2.3.1" } }, "sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -1332,9 +1347,7 @@ "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], - "@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="], - - "@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="], + "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], "@octokit/auth-app": ["@octokit/auth-app@8.0.1", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.1", "@octokit/auth-oauth-user": "^6.0.0", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg=="], @@ -1644,55 +1657,55 @@ "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], @@ -1726,107 +1739,107 @@ "@slack/web-api": ["@slack/web-api@6.13.0", "", { "dependencies": { "@slack/logger": "^3.0.0", "@slack/types": "^2.11.0", "@types/is-stream": "^1.1.0", "@types/node": ">=12.0.0", "axios": "^1.7.4", "eventemitter3": "^3.1.0", "form-data": "^2.5.0", "is-electron": "2.2.2", "is-stream": "^1.1.0", "p-queue": "^6.6.1", "p-retry": "^4.0.0" } }, "sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g=="], - "@smithy/abort-controller": ["@smithy/abort-controller@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw=="], + "@smithy/abort-controller": ["@smithy/abort-controller@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ=="], - "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="], + "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="], - "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="], + "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="], - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.10", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg=="], - "@smithy/core": ["@smithy/core@3.23.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA=="], + "@smithy/core": ["@smithy/core@3.23.9", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.12", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-stream": "^4.5.17", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ=="], - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="], + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g=="], "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="], - "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw=="], + "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.11", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA=="], - "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ=="], + "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA=="], - "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A=="], + "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.11", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw=="], - "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.8", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ=="], + "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.11", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q=="], - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ=="], - "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.9", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg=="], + "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.12", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-1wQE33DsxkM/waftAhCH9VtJbUGyt1PJ9YRDpOu+q9FUi73LLFUZ2fD8A61g2mT1UY9k7b99+V1xZ41Rz4SHRQ=="], - "@smithy/hash-node": ["@smithy/hash-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA=="], + "@smithy/hash-node": ["@smithy/hash-node@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A=="], - "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w=="], + "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-hQsTjwPCRY8w9GK07w1RqJi3e+myh0UaOWBBhZ1UMSDgofH/Q1fEYzU1teaX6HkpX/eWDdm7tAGR0jBPlz9QEQ=="], - "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ=="], + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g=="], - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], - "@smithy/md5-js": ["@smithy/md5-js@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ=="], + "@smithy/md5-js": ["@smithy/md5-js@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-350X4kGIrty0Snx2OWv7rPM6p6vM7RzryvFs6B/56Cux3w3sChOb3bymo5oidXJlPcP9fIRxGUCk7GqpiSOtng=="], - "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="], + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw=="], - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.16", "", { "dependencies": { "@smithy/core": "^3.23.2", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA=="], + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.23", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-serde": "^4.2.12", "@smithy/node-config-provider": "^4.3.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-middleware": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw=="], - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.33", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA=="], + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.40", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/protocol-http": "^5.3.11", "@smithy/service-error-classification": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-YhEMakG1Ae57FajERdHNZ4ShOPIY7DsgV+ZoAxo/5BT0KIe+f6DDU2rtIymNNFIj22NJfeeI6LWIifrwM0f+rA=="], - "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="], + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng=="], - "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA=="], + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg=="], - "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="], + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.10", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.14", "", { "dependencies": { "@smithy/abort-controller": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/querystring-builder": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A=="], - "@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="], + "@smithy/property-provider": ["@smithy/property-provider@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg=="], - "@smithy/protocol-http": ["@smithy/protocol-http@5.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ=="], + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ=="], - "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw=="], + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA=="], - "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA=="], + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ=="], - "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0" } }, "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ=="], + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw=="], - "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.3", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg=="], + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.6", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw=="], - "@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="], + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.11", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ=="], - "@smithy/smithy-client": ["@smithy/smithy-client@4.11.5", "", { "dependencies": { "@smithy/core": "^3.23.2", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ=="], + "@smithy/smithy-client": ["@smithy/smithy-client@4.12.3", "", { "dependencies": { "@smithy/core": "^3.23.9", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-stack": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.17", "tslib": "^2.6.2" } }, "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw=="], - "@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="], + "@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], - "@smithy/url-parser": ["@smithy/url-parser@4.2.8", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA=="], + "@smithy/url-parser": ["@smithy/url-parser@4.2.11", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing=="], - "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], - "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], - "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], - "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.32", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg=="], + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.39", "", { "dependencies": { "@smithy/property-provider": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-ui7/Ho/+VHqS7Km2wBw4/Ab4RktoiSshgcgpJzC4keFPs6tLJS4IQwbeahxQS3E/w98uq6E1mirCH/id9xIXeQ=="], - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.35", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q=="], + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.42", "", { "dependencies": { "@smithy/config-resolver": "^4.4.10", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-QDA84CWNe8Akpj15ofLO+1N3Rfg8qa2K5uX0y6HnOp4AnRYRgWrKx/xzbYNbVF9ZsyJUYOfcoaN3y93wA/QJ2A=="], - "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="], + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA=="], - "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], - "@smithy/util-middleware": ["@smithy/util-middleware@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A=="], + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.11", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw=="], - "@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="], + "@smithy/util-retry": ["@smithy/util-retry@4.2.11", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw=="], - "@smithy/util-stream": ["@smithy/util-stream@4.5.12", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg=="], + "@smithy/util-stream": ["@smithy/util-stream@4.5.17", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.13", "@smithy/node-http-handler": "^4.4.14", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ=="], - "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], - "@smithy/util-waiter": ["@smithy/util-waiter@4.2.8", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg=="], + "@smithy/util-waiter": ["@smithy/util-waiter@4.2.11", "", { "dependencies": { "@smithy/abort-controller": "^4.2.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-x7Rh2azQPs3XxbvCzcttRErKKvLnbZfqRf/gOjw2pb+ZscX88e5UkRPCB67bVnsFHxayvMvmePfKTqsRb+is1A=="], - "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], @@ -1838,7 +1851,7 @@ "@solid-primitives/event-bus": ["@solid-primitives/event-bus@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-l+n10/51neGcMaP3ypYt21bXfoeWh8IaC8k7fYuY3ww2a8S1Zv2N2a7FF5Qn+waTu86l0V8/nRHjkyqVIZBYwA=="], - "@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="], + "@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.5", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA=="], "@solid-primitives/i18n": ["@solid-primitives/i18n@2.2.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-TnTnE2Ku11MGYZ1JzhJ8pYscwg1fr9MteoYxPwsfxWfh9Jp5K7RRJncJn9BhOHvNLwROjqOHZ46PT7sPHqbcXw=="], @@ -1848,25 +1861,25 @@ "@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="], - "@solid-primitives/props": ["@solid-primitives/props@3.2.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-lZOTwFJajBrshSyg14nBMEP0h8MXzPowGO0s3OeiR3z6nXHTfj0FhzDtJMv+VYoRJKQHG2QRnJTgCzK6erARAw=="], + "@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="], - "@solid-primitives/refs": ["@solid-primitives/refs@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg=="], + "@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="], "@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="], - "@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ=="], + "@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="], "@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA=="], "@solid-primitives/scroll": ["@solid-primitives/scroll@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Ejq/Z7zKo/6eIEFr1bFLzXFxiGBCMLuqCM8QB8urr3YdPzjSETFLzYRWUyRiDWaBQN0F7k0SY6S7ig5nWOP7vg=="], - "@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw=="], + "@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ=="], "@solid-primitives/storage": ["@solid-primitives/storage@4.3.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "@tauri-apps/plugin-store": "*", "solid-js": "^1.6.12" }, "optionalPeers": ["@tauri-apps/plugin-store"] }, "sha512-ACbNwMZ1s8VAvld6EUXkDkX/US3IhtlPLxg6+B2s9MwNUugwdd51I98LPEaHrdLpqPmyzqgoJe0TxEFlf3Dqrw=="], - "@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-IWoptVc0SWYgmpBPpCMehS5b07+tpFcvw15tOQ3QbXedSYn6KP8zCjPkHNzMxcOvOicTneleeZDP7lqmz+PQ6g=="], + "@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng=="], - "@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="], + "@solid-primitives/utils": ["@solid-primitives/utils@6.4.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A=="], "@solid-primitives/websocket": ["@solid-primitives/websocket@1.3.1", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-F06tA2FKa5VsnS4E4WEc3jHpsJfXRlMTGOtolugTzCqV3JmJTyvk9UVg1oz6PgGHKGi1CQ91OP8iW34myyJgaQ=="], @@ -1874,7 +1887,7 @@ "@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="], - "@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@dfb2020", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite": "7.1.10", "vite-plugin-solid": "^2.11.9", "vitest": "^4.0.10" } }], + "@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@dfb2020", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite": "7.1.10", "vite-plugin-solid": "^2.11.9", "vitest": "^4.0.10" } }, "sha512-7JjjA49VGNOsMRI8QRUhVudZmv0CnJ18SliSgK1ojszs/c3ijftgVkzvXdkSLN4miDTzbkXewf65D6ZBo6W+GQ=="], "@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="], @@ -1884,29 +1897,29 @@ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - "@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-zuR1n1xgWoieEnr6E5xdTR40BI61IBQahgmsRpTvqRffL3mxAs5aFoORDmA5pZWI2LE9URdMkY85h218ijuLiw=="], + "@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.17" } }, "sha512-J0ogEc4/XFC+Ytz+X1we6TOKreEk/shgUs/mtxdsLa0xJ6bp2n2OQPSjNtQHH/nK4SRBSfHWPm8ztfcXTzeG9w=="], - "@storybook/addon-docs": ["@storybook/addon-docs@10.2.13", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.13", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.13", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-puMxpJbt/CuodLIbKDxWrW1ZgADYomfNHWEKp2d2l2eJjp17rADx0h3PABuNbX+YHbJwYcDdqluSnQwMysFEOA=="], + "@storybook/addon-docs": ["@storybook/addon-docs@10.2.17", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.17", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.17", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.17" } }, "sha512-c414xi7rxlaHn92qWOxtEkcOMm0/+cvBui0gUsgiWOZOM8dHChGZ/RjMuf1pPDyOrSsybLsPjZhP0WthsMDkdQ=="], - "@storybook/addon-links": ["@storybook/addon-links@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.13" }, "optionalPeers": ["react"] }, "sha512-8wnAomGiHaUpNIc+lOzmazTrebxa64z9rihIbM/Q59vkOImHQNkGp7KP/qNgJA4GPTFtu8+fLjX2qCoAQPM0jQ=="], + "@storybook/addon-links": ["@storybook/addon-links@10.2.17", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.17" }, "optionalPeers": ["react"] }, "sha512-KY2usxhPpt9AAzD22uBEfdPj1NZyCNyaYXgKkr8r/UeCNt7E7OdVBLNA1QMYZZ5dtIWj9EtY8c55OPuBM7aUkQ=="], - "@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.13", "", { "peerDependencies": { "storybook": "^10.2.13" } }, "sha512-kw2GgIY67UR8YXKfuVS0k+mfWL1joNQHeSe5DlDL4+7qbgp9zfV6cRJ199BMdfRAQNMzQoxHgRUcAMAqs3Rkpw=="], + "@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.2.17", "", { "peerDependencies": { "storybook": "^10.2.17" } }, "sha512-+WQC4RJlIFXF+ww2aNsq0pg8JaM5el29WQ9Hd2VZrB9LdjTqckuJI+5oQBZ1GFQNQDPxlif2TJfRGgI3m1wSpA=="], - "@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.13", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-qQD3xzxc31cQHS0loF9enGWi5sgA6zBTbaJ0HuSUNGO81iwfLSALh8L/1vrD5NfN2vlBeUMTsgv3EkCuLfe9EQ=="], + "@storybook/addon-vitest": ["@storybook/addon-vitest@10.2.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.2.17", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-47mo952M/dHZQn1yTVMEUnri5KuIwWynPqamv6Q9KFXrSPOnBt/8IdrTcPUXFo5XO1ZmIWclgQjJtIvGe4z+ag=="], - "@storybook/builder-vite": ["@storybook/builder-vite@10.2.10", "", { "dependencies": { "@storybook/csf-plugin": "10.2.10", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-Wd6CYL7LvRRNiXMz977x9u/qMm7nmMw/7Dow2BybQo+Xbfy1KhVjIoZ/gOiG515zpojSozctNrJUbM0+jH1jwg=="], + "@storybook/builder-vite": ["@storybook/builder-vite@10.2.17", "", { "dependencies": { "@storybook/csf-plugin": "10.2.17", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.17", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-m/OBveTLm5ds/tUgHmmbKzgSi/oeCpQwm5rZa49vP2BpAd41Q7ER6TzkOoISzPoNNMAcbVmVc5vn7k6hdbPSHw=="], - "@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.13", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.13", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-gUCR7PmyrWYj3dIJJgxOm25dcXFolPIUPmug3z90Aaon7YPXw3pUN+dNDx8KqDJqRK1WDIB4HaefgYZIm5V7iA=="], + "@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.17", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.17", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-crHH8i/4mwzeXpWRPgwvwX2vjytW42zyzTRySUax5dTU8o9sjk4y+Z9hkGx3Nmu1TvqseS8v1Z20saZr/tQcWw=="], "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="], "@storybook/icons": ["@storybook/icons@2.0.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg=="], - "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.13", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.13" } }, "sha512-ZSduoB10qTI0V9z22qeULmQLsvTs8d/rtJi03qbVxpPiMRor86AmyAaBrfhGGmWBxWQZpOGQQm6yIT2YLoPs7w=="], + "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.17", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.17" } }, "sha512-x9Kb7eUSZ1zGsEw/TtWrvs1LwWIdNp8qoOQCgPEjdB07reSJcE8R3+ASWHJThmd4eZf66ZALPJyerejake4Osw=="], "@stripe/stripe-js": ["@stripe/stripe-js@8.6.1", "", {}, "sha512-UJ05U2062XDgydbUcETH1AoRQLNhigQ2KmDn1BG8sC3xfzu6JKg95Qt6YozdzFpxl1Npii/02m2LEWFt1RYjVA=="], - "@swc/helpers": ["@swc/helpers@0.5.18", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ=="], + "@swc/helpers": ["@swc/helpers@0.5.19", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA=="], "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], @@ -1948,29 +1961,29 @@ "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="], - "@tauri-apps/cli": ["@tauri-apps/cli@2.10.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.0", "@tauri-apps/cli-darwin-x64": "2.10.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", "@tauri-apps/cli-linux-arm64-musl": "2.10.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-musl": "2.10.0", "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", "@tauri-apps/cli-win32-x64-msvc": "2.10.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q=="], + "@tauri-apps/cli": ["@tauri-apps/cli@2.10.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.1", "@tauri-apps/cli-darwin-x64": "2.10.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", "@tauri-apps/cli-linux-arm64-musl": "2.10.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-musl": "2.10.1", "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", "@tauri-apps/cli-win32-x64-msvc": "2.10.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g=="], - "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA=="], + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ=="], - "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q=="], + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw=="], - "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.0", "", { "os": "linux", "cpu": "arm" }, "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g=="], + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w=="], - "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA=="], + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA=="], - "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA=="], + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg=="], - "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.0", "", { "os": "linux", "cpu": "none" }, "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw=="], + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.1", "", { "os": "linux", "cpu": "none" }, "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw=="], - "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng=="], + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw=="], - "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q=="], + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ=="], - "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g=="], + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg=="], - "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A=="], + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw=="], - "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="], + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="], "@tauri-apps/plugin-clipboard-manager": ["@tauri-apps/plugin-clipboard-manager@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ=="], @@ -2008,8 +2021,6 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="], - "@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="], "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], @@ -2100,7 +2111,7 @@ "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], - "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + "@types/qs": ["@types/qs@6.15.0", "", {}, "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow=="], "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], @@ -2118,7 +2129,7 @@ "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="], - "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], + "@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], @@ -2164,7 +2175,7 @@ "@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="], - "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.3", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA=="], + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.4", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -2216,7 +2227,7 @@ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -2226,8 +2237,6 @@ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], - "ai": ["ai@5.0.124", "", { "dependencies": { "@ai-sdk/gateway": "2.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Li6Jw9F9qsvFJXZPBfxj38ddP2iURCnMs96f9Q3OeQzrDVcl1hvtwSEAuxA/qmfh6SDV2ERqFUOFzigvr0697g=="], "ai-gateway-provider": ["ai-gateway-provider@2.3.1", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.19", "ai": "^5.0.116" }, "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^3.0.71", "@ai-sdk/anthropic": "^2.0.56", "@ai-sdk/azure": "^2.0.90", "@ai-sdk/cerebras": "^1.0.33", "@ai-sdk/cohere": "^2.0.21", "@ai-sdk/deepgram": "^1.0.21", "@ai-sdk/deepseek": "^1.0.32", "@ai-sdk/elevenlabs": "^1.0.21", "@ai-sdk/fireworks": "^1.0.30", "@ai-sdk/google": "^2.0.51", "@ai-sdk/google-vertex": "3.0.90", "@ai-sdk/groq": "^2.0.33", "@ai-sdk/mistral": "^2.0.26", "@ai-sdk/openai": "^2.0.88", "@ai-sdk/perplexity": "^2.0.22", "@ai-sdk/xai": "^2.0.42", "@openrouter/ai-sdk-provider": "^1.5.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^1.0.29" } }, "sha512-PqI6TVNEDNwr7kOhy7XUGnA8XJB1SpeA9aLqGjr0CyWkKgH+y+ofPm8MZGZ74DOwVejDF+POZq0Qs9jKEKUeYg=="], @@ -2260,16 +2269,12 @@ "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="], - "aproba": ["aproba@2.1.0", "", {}, "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew=="], - "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], "arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="], - "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], - "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -2302,7 +2307,7 @@ "astro": ["astro@5.7.13", "", { "dependencies": { "@astrojs/compiler": "^2.11.0", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.1", "@astrojs/telemetry": "3.2.1", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w=="], - "astro-expressive-code": ["astro-expressive-code@0.41.6", "", { "dependencies": { "rehype-expressive-code": "^0.41.6" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" } }, "sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw=="], + "astro-expressive-code": ["astro-expressive-code@0.41.7", "", { "dependencies": { "rehype-expressive-code": "^0.41.7" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" } }, "sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ=="], "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], @@ -2318,7 +2323,7 @@ "atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="], - "autoprefixer": ["autoprefixer@10.4.24", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw=="], + "autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -2334,11 +2339,11 @@ "axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="], - "axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="], + "axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], - "b4a": ["b4a@1.7.5", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-iEsKNwDh1wiWTps1/hdkNdmBgDlDVZP5U57ZVOlt+dNFqpc/lpPouCIxZw+DYBgc4P9NDfIZMPNR4CHNhzwLIA=="], + "b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="], "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], @@ -2350,15 +2355,25 @@ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "balanced-match": ["balanced-match@4.0.2", "", { "dependencies": { "jackspeak": "^4.2.3" } }, "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], + "bare-fs": ["bare-fs@4.5.5", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w=="], + + "bare-os": ["bare-os@3.7.1", "", {}, "sha512-ebvMaS5BgZKmJlvuWh14dg9rbUI84QeV3WlWn6Ph6lFI8jJoh7ADtVTyD2c93euwbe+zgi0DVrl4YmqXeM9aIA=="], + + "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], + + "bare-stream": ["bare-stream@2.8.0", "", { "dependencies": { "streamx": "^2.21.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA=="], + + "bare-url": ["bare-url@2.3.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw=="], + "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], @@ -2372,8 +2387,6 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], - "bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="], "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], @@ -2396,7 +2409,7 @@ "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], - "brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="], + "brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -2442,7 +2455,7 @@ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - "cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="], + "cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], @@ -2460,7 +2473,7 @@ "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -2500,8 +2513,6 @@ "clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="], - "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], - "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], @@ -2558,8 +2569,6 @@ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], - "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -2594,7 +2603,7 @@ "css-selector-parser": ["css-selector-parser@3.3.0", "", {}, "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g=="], - "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], @@ -2626,8 +2635,6 @@ "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], - "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], @@ -2648,8 +2655,6 @@ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], - "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -2670,7 +2675,7 @@ "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], - "devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="], + "devalue": ["devalue@5.6.3", "", {}, "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -2726,10 +2731,12 @@ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - "editorconfig": ["editorconfig@1.0.4", "", { "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", "minimatch": "9.0.1", "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q=="], + "editorconfig": ["editorconfig@1.0.7", "", { "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", "minimatch": "^9.0.1", "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw=="], "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "effect": ["effect@4.0.0-beta.29", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-7UoBAEiktoS81XLMX/39Mq/Ymq8whxmqFpsI0MEYdMlbDcbytzQlyuyhvrwEIdrd9qrqa8DZ5mKblWasamryqw=="], + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], "electron": ["electron@40.4.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-N1ZXybQZL8kYemO8vAeh9nrk4mSvqlAO8xs0QCHkXIvRnuB/7VGwEehjvQbsU5/f4bmTKpG+2GQERe/zmKpudQ=="], @@ -2744,7 +2751,7 @@ "electron-store": ["electron-store@10.1.0", "", { "dependencies": { "conf": "^14.0.0", "type-fest": "^4.41.0" } }, "sha512-oL8bRy7pVCLpwhmXy05Rh/L6O93+k9t6dqSw0+MckIc3OmCTZm6Mp04Q4f/J0rtu84Ky6ywkR8ivtGOmrq+16w=="], - "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], + "electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="], "electron-updater": ["electron-updater@6.8.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ=="], @@ -2770,7 +2777,7 @@ "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], - "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], + "enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="], "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -2852,8 +2859,6 @@ "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], - "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], @@ -2862,7 +2867,7 @@ "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], - "expressive-code": ["expressive-code@0.41.6", "", { "dependencies": { "@expressive-code/core": "^0.41.6", "@expressive-code/plugin-frames": "^0.41.6", "@expressive-code/plugin-shiki": "^0.41.6", "@expressive-code/plugin-text-markers": "^0.41.6" } }, "sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA=="], + "expressive-code": ["expressive-code@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7", "@expressive-code/plugin-frames": "^0.41.7", "@expressive-code/plugin-shiki": "^0.41.7", "@expressive-code/plugin-text-markers": "^0.41.7" } }, "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA=="], "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], @@ -2874,6 +2879,8 @@ "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], + "fast-check": ["fast-check@4.6.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA=="], + "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], @@ -2892,9 +2899,11 @@ "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fast-xml-builder": ["fast-xml-builder@1.0.0", "", {}, "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ=="], + "fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="], - "fastify": ["fastify@5.7.4", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA=="], + "fastify": ["fastify@5.8.2", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.14.0 || ^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-lZmt3navvZG915IE+f7/TIVamxIwmBd+OMB+O9WBzcpIwOo6F0LTh0sluoMFk5VkrKTvvrwIaoJPkir4Z+jtAg=="], "fastify-plugin": ["fastify-plugin@5.1.0", "", {}, "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw=="], @@ -2908,8 +2917,6 @@ "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], - "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], - "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -2918,7 +2925,9 @@ "find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="], - "find-my-way": ["find-my-way@9.4.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w=="], + "find-my-way": ["find-my-way@9.5.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ=="], + + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -2952,11 +2961,9 @@ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], - "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], - "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -2970,9 +2977,7 @@ "fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="], - "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], - - "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], @@ -2984,7 +2989,7 @@ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -2998,16 +3003,12 @@ "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], - "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@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=="], "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], - "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], - "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], @@ -3032,7 +3033,7 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="], + "graphql": ["graphql@16.13.1", "", {}, "sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ=="], "graphql-request": ["graphql-request@6.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" }, "peerDependencies": { "graphql": "14 - 16" } }, "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw=="], @@ -3042,7 +3043,7 @@ "h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="], - "happy-dom": ["happy-dom@20.6.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-Xk/Y0cuq9ngN/my8uvK4gKoyDl6sBKkIl8A/hJ0IabZVH7E5SJLHNE7uKRPVmSrQbhJaLIHTEcvTct4GgNtsRA=="], + "happy-dom": ["happy-dom@20.8.3", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-lMHQRRwIPyJ70HV0kkFT7jH/gXzSI7yDkQFe07E2flwmNDFoWUTRMKpW2sglsnpeA7b6S2TJPp98EbQxai8eaQ=="], "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], @@ -3056,8 +3057,6 @@ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hast-util-embedded": ["hast-util-embedded@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA=="], @@ -3164,13 +3163,11 @@ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], - "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="], - "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], @@ -3236,8 +3233,6 @@ "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], - "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], - "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -3340,6 +3335,8 @@ "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json-with-bigint": ["json-with-bigint@3.5.7", "", {}, "sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], @@ -3364,6 +3361,8 @@ "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + "lang-map": ["lang-map@0.4.0", "", { "dependencies": { "language-map": "^1.1.0" } }, "sha512-oiSqZIEUnWdFeDNsp4HId4tAxdFbx5iMBOwA3666Fn2L8Khj8NiD9xRvMsGmKXopPVkaDFtSv3CJOmXFUB0Hcg=="], "language-map": ["language-map@1.5.0", "", {}, "sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg=="], @@ -3454,7 +3453,7 @@ "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], - "make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="], + "make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], @@ -3478,7 +3477,7 @@ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], @@ -3508,7 +3507,7 @@ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], - "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], @@ -3616,11 +3615,11 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], - "minipass-fetch": ["minipass-fetch@1.4.1", "", { "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "optionalDependencies": { "encoding": "^0.1.12" } }, "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw=="], + "minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], @@ -3632,8 +3631,6 @@ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], - "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], - "morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], "motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="], @@ -3646,12 +3643,18 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + "mssql": ["mssql@11.0.1", "", { "dependencies": { "@tediousjs/connection-string": "^0.5.0", "commander": "^11.0.0", "debug": "^4.3.3", "rfdc": "^1.3.0", "tarn": "^3.0.2", "tedious": "^18.2.1" }, "bin": { "mssql": "bin/mssql" } }, "sha512-KlGNsugoT90enKlR8/G36H0kTxPthDhmtNUCwEHvgRza5Cjpjoj+P2X6eMpFUDN7pFrJZsKadL4x990G8RBE1w=="], "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], "multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="], + "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], "mysql2": ["mysql2@3.14.4", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w=="], @@ -3664,8 +3667,6 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], - "native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="], "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], @@ -3680,7 +3681,7 @@ "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], - "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], + "node-abi": ["node-abi@4.26.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw=="], "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], @@ -3692,15 +3693,17 @@ "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - "node-gyp": ["node-gyp@8.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w=="], + "node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], - "node-html-parser": ["node-html-parser@7.0.2", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-DxodLVh7a6JMkYzWyc8nBX9MaF4M0lLFYkJHlWOiu7+9/I6mwNK9u5TbAMC7qfqDJEPX9OIoWA2A9t4C2l1mUQ=="], + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="], "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], @@ -3710,8 +3713,6 @@ "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], - "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="], @@ -3780,7 +3781,7 @@ "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], @@ -3832,7 +3833,7 @@ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], @@ -3890,7 +3891,7 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "postcss-css-variables": ["postcss-css-variables@0.18.0", "", { "dependencies": { "balanced-match": "^1.0.0", "escape-string-regexp": "^1.0.3", "extend": "^3.0.1" }, "peerDependencies": { "postcss": "^8.2.6" } }, "sha512-lYS802gHbzn1GI+lXvy9MYIYDuGnl1WB4FTKoqMQqJ3Mab09A7a/1wZvGTkCEZJTM8mSbIyb1mJYn8f0aPye0Q=="], @@ -3912,8 +3913,6 @@ "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], - "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], - "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="], @@ -3932,8 +3931,6 @@ "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], - "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], - "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], "promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="], @@ -3954,6 +3951,8 @@ "punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="], + "pure-rand": ["pure-rand@8.0.0", "", {}, "sha512-7rgWlxG2gAvFPIQfUreo1XYlNvrQ9VnQPFWdncPkdl3icucLK0InOxsaafbvxGTnI6Bk/Rxmslg0lQlRCuzOXw=="], + "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], @@ -3972,8 +3971,6 @@ "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], - "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], @@ -4036,7 +4033,7 @@ "rehype-autolink-headings": ["rehype-autolink-headings@7.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-is-element": "^3.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw=="], - "rehype-expressive-code": ["rehype-expressive-code@0.41.6", "", { "dependencies": { "expressive-code": "^0.41.6" } }, "sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g=="], + "rehype-expressive-code": ["rehype-expressive-code@0.41.7", "", { "dependencies": { "expressive-code": "^0.41.7" } }, "sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ=="], "rehype-format": ["rehype-format@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-format": "^1.0.0" } }, "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ=="], @@ -4084,8 +4081,6 @@ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], @@ -4108,11 +4103,11 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], - "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], - "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], @@ -4166,8 +4161,6 @@ "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], - "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], - "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], @@ -4200,10 +4193,6 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], - - "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], @@ -4212,7 +4201,7 @@ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - "sitemap": ["sitemap@8.0.2", "", { "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/cli.js" } }, "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ=="], + "sitemap": ["sitemap@8.0.3", "", { "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/cli.js" } }, "sha512-9Ew1tR2WYw8RGE2XLy7GjkusvYXy8Rg6y8TYuBuQMfIEdGcWoJpY2Wr5DzsEiL/TKCw56+YKTCCUHglorEYK+A=="], "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], @@ -4228,7 +4217,7 @@ "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], - "socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="], + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], @@ -4258,13 +4247,11 @@ "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], - "sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="], - "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], "srvx": ["srvx@0.9.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ=="], - "ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="], + "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], "sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="], @@ -4300,7 +4287,7 @@ "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], - "storybook": ["storybook@10.2.13", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-heMfJjOfbHvL+wlCAwFZlSxcakyJ5yQDam6e9k2RRArB1veJhRnsjO6lO1hOXjJYrqxfHA/ldIugbBVlCDqfvQ=="], + "storybook": ["storybook@10.2.17", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-yueTpl5YJqLzQqs3CanxNdAAfFU23iP0j+JVJURE4ghfEtRmWfWoZWLGkVcyjmgum7UmjwAlqRuOjQDNvH89kw=="], "storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.9", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="], @@ -4332,8 +4319,6 @@ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], - "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - "stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="], "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], @@ -4364,16 +4349,16 @@ "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], - "tar": ["tar@7.5.9", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg=="], + "tar": ["tar@7.5.11", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ=="], - "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], - - "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], + "tar-stream": ["tar-stream@3.1.8", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="], "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="], "tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="], + "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], + "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], @@ -4428,6 +4413,8 @@ "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], + "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="], + "toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.8", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-b+5ynEFp4Woe5a22hzNQm42lD23t13ZMihVxHbzjA50zdcM9aOSJTIjdJ0PDSd4/50HbBXcpHiQsz6rM4N88ww=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], @@ -4456,12 +4443,8 @@ "tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="], - "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], - "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], - "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+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.8.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA=="], @@ -4524,9 +4507,9 @@ "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], - "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="], + "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], - "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="], + "unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], @@ -4558,7 +4541,7 @@ "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], - "unstorage": ["unstorage@2.0.0-alpha.5", "", { "peerDependencies": { "@azure/app-configuration": "^1.9.0", "@azure/cosmos": "^4.7.0", "@azure/data-tables": "^13.3.1", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.29.1", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.12.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.35.6", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.8.2", "lru-cache": "^11.2.2", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g=="], + "unstorage": ["unstorage@2.0.0-alpha.6", "", { "peerDependencies": { "@azure/app-configuration": "^1.11.0", "@azure/cosmos": "^4.9.1", "@azure/data-tables": "^13.3.2", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.31.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.13.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.36.2", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.9.3", "lru-cache": "^11.2.6", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-w5vLYCJtnSx3OBtDk7cG4c1p3dfAnHA4WSZq9Xsurjbl2wMj7zqfOIjaHQI1Bl7yKzUxXAi+kbMr8iO2RhJmBA=="], "unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="], @@ -4584,7 +4567,7 @@ "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], - "uuid": ["uuid@8.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="], + "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], @@ -4606,7 +4589,7 @@ "vite-plugin-solid": ["vite-plugin-solid@2.11.10", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="], - "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + "vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="], "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="], @@ -4624,9 +4607,9 @@ "volar-service-yaml": ["volar-service-yaml@0.0.68", "", { "dependencies": { "vscode-uri": "^3.0.8", "yaml-language-server": "~1.19.2" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-84XgE02LV0OvTcwfqhcSwVg4of3MLNUWPMArO6Aj8YXqyEVnPu8xTEMY2btKSq37mVAPuaEVASI4e3ptObmqcA=="], - "vscode-css-languageservice": ["vscode-css-languageservice@6.3.9", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-1tLWfp+TDM5ZuVWht3jmaY5y7O6aZmpeXLoHl5bv1QtRsRKt4xYGRMmdJa5Pqx/FTkgRbsna9R+Gn2xE+evVuA=="], + "vscode-css-languageservice": ["vscode-css-languageservice@6.3.10", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA=="], - "vscode-html-languageservice": ["vscode-html-languageservice@5.6.1", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-5Mrqy5CLfFZUgkyhNZLA1Ye5g12Cb/v6VM7SxUzZUaRKWMDz4md+y26PrfRTSU0/eQAl3XpO9m2og+GGtDMuaA=="], + "vscode-html-languageservice": ["vscode-html-languageservice@5.6.2", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg=="], "vscode-json-languageservice": ["vscode-json-languageservice@4.1.8", "", { "dependencies": { "jsonc-parser": "^3.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" } }, "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg=="], @@ -4676,8 +4659,6 @@ "why-is-node-running": ["why-is-node-running@3.2.2", "", { "bin": { "why-is-node-running": "cli.js" } }, "sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A=="], - "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], - "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], "workerd": ["workerd@1.20251118.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251118.0", "@cloudflare/workerd-darwin-arm64": "1.20251118.0", "@cloudflare/workerd-linux-64": "1.20251118.0", "@cloudflare/workerd-linux-arm64": "1.20251118.0", "@cloudflare/workerd-windows-64": "1.20251118.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ=="], @@ -4698,7 +4679,7 @@ "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="], - "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], @@ -4758,8 +4739,6 @@ "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="], - "@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="], - "@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="], @@ -4774,9 +4753,17 @@ "@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="], + "@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], - "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], + "@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="], + + "@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="], + + "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AnGoxVNZ/E3EU4lW12rrufI6riqL2cEv4jk3OrjJ/i54XwR0CJU1V26jXAwxb+Pc+uZmYG++HM+gzXxPQZkMNQ=="], + + "@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="], "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], @@ -4822,53 +4809,47 @@ "@astrojs/solid-js/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], + "@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + + "@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + + "@aws-crypto/sha1-browser/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-crypto/sha256-browser/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-crypto/sha256-js/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + + "@aws-crypto/util/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.10", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.19", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.17", "@aws-sdk/credential-provider-http": "^3.972.19", "@aws-sdk/credential-provider-ini": "^3.972.18", "@aws-sdk/credential-provider-process": "^3.972.17", "@aws-sdk/credential-provider-sso": "^3.972.18", "@aws-sdk/credential-provider-web-identity": "^3.972.18", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-yDWQ9dFTr+IMxwanFe7+tbN5++q8psZBjlUwOiCXn1EzANoBgtqBwcpYcHaMGtn0Wlfj4NuXdf2JaEx1lz5RaQ=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], "@aws-sdk/client-cognito-identity/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], - "@aws-sdk/client-sso/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], - - "@aws-sdk/client-sso/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="], - - "@aws-sdk/client-sso/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="], - - "@aws-sdk/client-sso/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="], - - "@aws-sdk/client-sso/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="], - - "@aws-sdk/client-sso/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="], - - "@aws-sdk/client-sso/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], - - "@aws-sdk/client-sso/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], - - "@aws-sdk/client-sso/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="], - - "@aws-sdk/client-sso/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="], + "@aws-sdk/client-sso/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], "@aws-sdk/client-sts/@aws-sdk/core": ["@aws-sdk/core@3.775.0", "", { "dependencies": { "@aws-sdk/types": "3.775.0", "@smithy/core": "^3.2.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.0.2", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA=="], @@ -4892,25 +4873,29 @@ "@aws-sdk/client-sts/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.782.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.980.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.5", "@aws-sdk/credential-provider-node": "^3.972.4", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.5", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.980.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-nLgMW2drTzv+dTo3ORCcotQPcrUaTQ+xoaDTdSaUXdZO7zbbVyk7ysE5GDTnJdZWcUjHOSB8xfNQhOTTNVPhFw=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.8", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/credential-provider-env/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-env/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/credential-provider-env/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-env/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/credential-provider-http/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-http/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/credential-provider-http/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-http/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/credential-provider-ini/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-ini/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/credential-provider-ini/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.8", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA=="], - "@aws-sdk/credential-provider-login/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-ini/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/credential-provider-login/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.8", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ozge/c7NdHUDyHqro6+P5oHt8wfKSUBN+olttiVfBe9Mw3wBMpPa3gQ0pZnG+gwBkKskBuip2bMR16tqYvUSEA=="], @@ -4924,47 +4909,53 @@ "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-c7Eccw2lhFx2/+qJn3g+uIDWRuWi2A6Sz3PVvckFUEzPsP0dPUo19hlvtarwP5GzrsXn0yEPRVhpewsIaSCGaQ=="], - "@aws-sdk/credential-provider-process/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-process/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/credential-provider-process/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-process/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.8", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA=="], - "@aws-sdk/credential-provider-web-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/credential-provider-web-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-provider-web-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/credential-providers/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.8", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA=="], - "@aws-sdk/credential-providers/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.10", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA=="], + "@aws-sdk/credential-provider-web-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/credential-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/credential-providers/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-providers/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.19", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.17", "@aws-sdk/credential-provider-http": "^3.972.19", "@aws-sdk/credential-provider-ini": "^3.972.18", "@aws-sdk/credential-provider-process": "^3.972.17", "@aws-sdk/credential-provider-sso": "^3.972.18", "@aws-sdk/credential-provider-web-identity": "^3.972.18", "@aws-sdk/types": "^3.973.5", "@smithy/credential-provider-imds": "^4.2.11", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-yDWQ9dFTr+IMxwanFe7+tbN5++q8psZBjlUwOiCXn1EzANoBgtqBwcpYcHaMGtn0Wlfj4NuXdf2JaEx1lz5RaQ=="], - "@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="], + "@aws-sdk/credential-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], - "@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="], + "@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], - "@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], - "@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], - "@aws-sdk/nested-clients/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], + + "@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], + + "@aws-sdk/nested-clients/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], "@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], - "@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="], + "@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], - "@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="], + "@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], - "@aws-sdk/token-providers/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/token-providers/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/token-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], + "@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.8", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.19", "@aws-sdk/middleware-host-header": "^3.972.7", "@aws-sdk/middleware-logger": "^3.972.7", "@aws-sdk/middleware-recursion-detection": "^3.972.7", "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/region-config-resolver": "^3.972.7", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@aws-sdk/util-user-agent-browser": "^3.972.7", "@aws-sdk/util-user-agent-node": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/core": "^3.23.9", "@smithy/fetch-http-handler": "^5.3.13", "@smithy/hash-node": "^4.2.11", "@smithy/invalid-dependency": "^4.2.11", "@smithy/middleware-content-length": "^4.2.11", "@smithy/middleware-endpoint": "^4.4.23", "@smithy/middleware-retry": "^4.4.40", "@smithy/middleware-serde": "^4.2.12", "@smithy/middleware-stack": "^4.2.11", "@smithy/node-config-provider": "^4.3.11", "@smithy/node-http-handler": "^4.4.14", "@smithy/protocol-http": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.39", "@smithy/util-defaults-mode-node": "^4.2.42", "@smithy/util-endpoints": "^3.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-retry": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA=="], + + "@aws-sdk/token-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="], "@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], @@ -4974,8 +4965,12 @@ "@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "@azure/core-http/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + "@azure/identity/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -5014,21 +5009,17 @@ "@electron/rebuild/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "@electron/rebuild/node-abi": ["node-abi@4.26.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw=="], - - "@electron/rebuild/node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], - "@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - "@electron/universal/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], + "@electron/universal/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], - "@electron/universal/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "@electron/universal/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "@electron/windows-sign/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], + "@electron/windows-sign/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], "@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], - "@gitlab/gitlab-ai-provider/openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="], + "@gitlab/gitlab-ai-provider/openai": ["openai@6.27.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-osTKySlrdYrLYTt0zjhY8yp0JUBmWDCN+Q+QxsV4xMQnnoVFpylgKGgxwN8sSdTNw0G4y+WUXs4eCMWpyDNWZQ=="], "@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -5086,7 +5077,7 @@ "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - "@modelcontextprotocol/sdk/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "@modelcontextprotocol/sdk/jose": ["jose@6.2.1", "", {}, "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw=="], "@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], @@ -5094,25 +5085,19 @@ "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "@npmcli/agent/socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], - - "@npmcli/move-file/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - - "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - - "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/auth-app/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], - "@octokit/auth-oauth-app/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/auth-oauth-app/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/auth-oauth-app/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], - "@octokit/auth-oauth-device/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/auth-oauth-device/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/auth-oauth-device/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], - "@octokit/auth-oauth-user/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/auth-oauth-user/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/auth-oauth-user/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], @@ -5126,11 +5111,11 @@ "@octokit/endpoint/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="], - "@octokit/graphql/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/graphql/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/graphql/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="], - "@octokit/oauth-methods/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/oauth-methods/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/oauth-methods/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -5216,7 +5201,21 @@ "@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], - "@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="], + "@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.11", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w=="], + + "@smithy/hash-node/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/hash-stream-node/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/md5-js/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/signature-v4/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/util-base64/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/util-stream/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], "@solidjs/start/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], @@ -5224,8 +5223,6 @@ "@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="], - "@storybook/builder-vite/@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="], - "@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], @@ -5252,8 +5249,6 @@ "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], - "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], @@ -5266,17 +5261,13 @@ "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], - "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.79", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.62", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GfAQUb1GEmdTjLu5Ud1d5sieNHDpwoQdb4S14KmJlA5RsGREUZ1tfSKngFaiClxFtL0xPSZjePhTMV6Z65A7/g=="], - - "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.63", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zXlUPCkumnvp8lWS9VFcen/MLF6CL/t1zAKDhpobYj9y/nmylQrKtRvn3RwH871Wd3dF3KYEUXd6M2c6dfCKOA=="], - - "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@2.0.53", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ccCxr5mrd3AC2CjLq4e1ST7+UiN5T2Pdmgi0XdWM3QohmNBwUQ/RBG7BvL+cB/ex/j6y64tkMmpYz9zBw/SEFQ=="], + "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.65", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HqTPP59mLQ9U6jXQcx6EORkdc5FyZu34Sitkg6jNpyMYcRjStvfx4+NWq/qaR+OTwBFcccv8hvVii0CYkH2Lag=="], "ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.90", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.56", "@ai-sdk/google": "2.0.46", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-C9MLe1KZGg1ZbupV2osygHtL5qngyCDA6ATatunyfTbIe8TXKG8HGni/3O6ifbnI5qxTidIn150Ox7eIFZVMYg=="], "ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@2.0.89", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4+qWkBCbL9HPKbgrUO/F2uXZ8GqrYxHa8SWEYIzxEJ9zvWw3ISr3t1/27O1i8MGSym+PzEyHBT48EV4LAwWaEw=="], - "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], + "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.34", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AnGoxVNZ/E3EU4lW12rrufI6riqL2cEv4jk3OrjJ/i54XwR0CJU1V26jXAwxb+Pc+uZmYG++HM+gzXxPQZkMNQ=="], "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], @@ -5288,7 +5279,7 @@ "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], - "app-builder-lib/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], + "app-builder-lib/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], @@ -5296,8 +5287,6 @@ "archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], - "are-we-there-yet/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], "astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], @@ -5310,7 +5299,7 @@ "aws-sdk/events": ["events@1.1.1", "", {}, "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="], - "aws-sdk/xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], + "aws-sdk/uuid": ["uuid@8.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="], "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], @@ -5324,11 +5313,9 @@ "body-parser/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], - "buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "builder-util-runtime/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "builder-util-runtime/sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="], "bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], @@ -5336,19 +5323,9 @@ "c12/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], - "cacache/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - "cacache/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "cacache/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - - "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "cacache/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - - "cacache/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - - "cacache/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -5362,12 +5339,12 @@ "conf/env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "config-chain/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "crc/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "db0/drizzle-orm": ["drizzle-orm@1.0.0-beta.12-a5629fb", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-wyOAgr9Cy9oEN6z5S0JGhfipLKbRRJtQKgbDO9SXGR9swMBbGNIlXkeMqPRrqYQ8k70mh+7ZJ/eVmJ2F7zR3Vg=="], - "defaults/clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], @@ -5384,7 +5361,9 @@ "editorconfig/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], - "editorconfig/minimatch": ["minimatch@9.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w=="], + "editorconfig/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -5422,7 +5401,7 @@ "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], - "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -5430,17 +5409,9 @@ "fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "gauge/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "gauge/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "gauge/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], + "glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -5470,13 +5441,7 @@ "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "make-fetch-happen/http-proxy-agent": ["http-proxy-agent@4.0.1", "", { "dependencies": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg=="], - - "make-fetch-happen/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - - "make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - - "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -5492,33 +5457,23 @@ "miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], - "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-fetch/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], - "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "motion/framer-motion": ["framer-motion@12.34.5", "", { "dependencies": { "motion-dom": "^12.34.5", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Z2dQ+o7BsfpJI3+u0SQUNCrN+ajCKJen1blC4rCHx1Ta2EOHs+xKJegLT2aaD9iSMbU3OoX+WabQXkloUbZmJQ=="], + "motion/framer-motion": ["framer-motion@12.35.2", "", { "dependencies": { "motion-dom": "^12.35.2", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA=="], "mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], - "node-gyp/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], - "node-gyp/nopt": ["nopt@5.0.0", "", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="], + "node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], - "node-gyp/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - - "node-gyp/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], - - "node-gyp/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], @@ -5558,6 +5513,8 @@ "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "parse-bmfont-xml/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -5576,8 +5533,6 @@ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], - "prebuild-install/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -5590,13 +5545,13 @@ "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - "readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], @@ -5616,20 +5571,12 @@ "shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], - "sitemap/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], - - "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - - "sqlite3/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], - - "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "sitemap/sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="], "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], "sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="], - "storybook/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], - "storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "storybook/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], @@ -5644,23 +5591,15 @@ "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], - - "tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], - "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], - "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "tree-sitter-bash/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="], - - "tsx/esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + "tree-sitter-bash/node-addon-api": ["node-addon-api@8.6.0", "", {}, "sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q=="], "tw-to-css/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], @@ -5690,8 +5629,6 @@ "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "wide-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -5700,8 +5637,6 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "xml2js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], - "yaml-language-server/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], @@ -5724,12 +5659,46 @@ "@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], + "@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/anthropic/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/deepgram/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/elevenlabs/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/gateway/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/openai/@ai-sdk/provider-utils/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], + "@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/togetherai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -5744,9 +5713,11 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], - "@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw=="], @@ -5760,57 +5731,171 @@ "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.782.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/nested-clients": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xCna0opVPaueEbJoclj5C6OpDNi0Gynj+4d7tnuXGgQhTHPyAz8ZyClkVqpi5qvHTgxROdUEDxWqEO5jqRHZHQ=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.973.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws-sdk/xml-builder": "^3.972.10", "@smithy/core": "^3.23.9", "@smithy/node-config-provider": "^4.3.11", "@smithy/property-provider": "^4.2.11", "@smithy/protocol-http": "^5.3.11", "@smithy/signature-v4": "^5.3.11", "@smithy/smithy-client": "^4.12.3", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.11", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.10", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.980.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], - "@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-env/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], - "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-http/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zwGLSiK48z3PzKpQiDMKP85+fpIrPMF1qQOQW9OW7BGj5AuBZIisT2O4VzIgYJeh+t47MLU7VgBQL7muc+MJDg=="], - "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qzq7zj9yXUgAAJEbbmqRhm0jmUndl8nHG0AbxFEfCfQRVZWL96Qzx0mf8lYwT9hIMrXncLwy31HOthmbXwFRwQ=="], "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="], - "@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-process/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], - "@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], - "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], - "@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], - "@azure/core-xml/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + + "@aws-sdk/credential-providers/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + + "@aws-sdk/nested-clients/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], + + "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], + + "@aws-sdk/token-providers/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.20", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/types": "^3.973.5", "@aws-sdk/util-endpoints": "^3.996.4", "@smithy/core": "^3.23.9", "@smithy/protocol-http": "^5.3.11", "@smithy/types": "^4.13.0", "@smithy/util-retry": "^4.2.11", "tslib": "^2.6.2" } }, "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/config-resolver": "^4.4.10", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.4", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.11", "@smithy/util-endpoints": "^3.3.2", "tslib": "^2.6.2" } }, "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.5", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.5", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.20", "@aws-sdk/types": "^3.973.5", "@smithy/node-config-provider": "^4.3.11", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw=="], + + "@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], + + "@azure/core-http/xml2js/sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="], + + "@azure/core-xml/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], + + "@azure/identity/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], @@ -5824,12 +5909,6 @@ "@electron/notarize/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - "@electron/rebuild/node-gyp/make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], - - "@electron/rebuild/node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], - - "@electron/rebuild/node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], - "@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -5896,7 +5975,7 @@ "@jsx-email/cli/vite/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - "@jsx-email/cli/vite/rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="], + "@jsx-email/cli/vite/rollup": ["rollup@3.30.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA=="], "@jsx-email/doiuse-email/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -5924,27 +6003,25 @@ "@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], - "@npmcli/move-file/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], "@octokit/auth-app/@octokit/request-error/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], - "@octokit/auth-oauth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/auth-oauth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/auth-oauth-app/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], "@octokit/auth-oauth-app/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@octokit/auth-oauth-device/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/auth-oauth-device/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/auth-oauth-device/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], "@octokit/auth-oauth-device/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@octokit/auth-oauth-user/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/auth-oauth-user/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/auth-oauth-user/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -5954,7 +6031,7 @@ "@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], - "@octokit/graphql/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/graphql/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/graphql/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -5962,7 +6039,7 @@ "@octokit/graphql/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@26.0.0", "", {}, "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA=="], - "@octokit/oauth-methods/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/oauth-methods/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/oauth-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -5970,7 +6047,7 @@ "@octokit/plugin-paginate-rest/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], - "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -5984,7 +6061,7 @@ "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], - "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -6004,7 +6081,7 @@ "@octokit/rest/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], - "@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + "@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], "@octokit/rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], @@ -6050,8 +6127,6 @@ "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="], - "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="], "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.46", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8PK6u4sGE/kXebd7ZkTp+0aya4kNqzoqpS5m7cHY2NfTK6fhPc6GNvE+MZIZIoHQTp5ed86wGBdeBPpFaaUtyg=="], @@ -6062,6 +6137,10 @@ "ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "ai-gateway-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw=="], + + "ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -6076,19 +6155,17 @@ "archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "archiver-utils/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], "astro/unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "astro/unstorage/h3": ["h3@1.15.5", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg=="], + "astro/unstorage/h3": ["h3@1.15.6", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-oi15ESLW5LRthZ+qPCi5GNasY/gvynSKUQxgiovrY63bPAtG59wtM+LSrlcwvOHAXzGrXVLnI97brbkdPF9WoQ=="], "astro/unstorage/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], - "aws-sdk/xml2js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], - - "babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="], + "babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], "babel-plugin-module-resolver/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], @@ -6100,11 +6177,11 @@ "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "cacache/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "cacache/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + "cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "cacache/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + "cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], "cli-truncate/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -6138,43 +6215,23 @@ "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "gauge/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "gauge/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "js-beautify/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "js-beautify/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], "js-beautify/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "lazystream/readable-stream/core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], - "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "make-fetch-happen/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "motion/framer-motion/motion-dom": ["motion-dom@12.35.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg=="], - "make-fetch-happen/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], - "motion/framer-motion/motion-dom": ["motion-dom@12.34.5", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-k33CsnxO2K3gBRMUZT+vPmc4Utlb5menKdG0RyVNLtlqRaaJPRWlE9fXl8NTtfZ5z3G8TDvqSu0MENLqSTaHZA=="], - - "node-gyp/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "node-gyp/nopt/abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], - - "node-gyp/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], - - "node-gyp/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], - - "node-gyp/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], - - "node-gyp/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - - "node-gyp/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], @@ -6196,6 +6253,8 @@ "ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "parse-bmfont-xml/xml2js/sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="], + "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -6204,136 +6263,14 @@ "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "sqlite3/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], - - "sqlite3/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], - - "sqlite3/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], - - "sqlite3/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - - "storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - - "storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - - "storybook/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - - "storybook/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - - "storybook/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - - "storybook/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - - "storybook/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - - "storybook/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - - "storybook/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - - "storybook/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - - "storybook/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - - "storybook/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - - "storybook/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - - "storybook/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - - "storybook/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - - "storybook/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - - "storybook/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - - "storybook/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - - "storybook/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - - "storybook/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - - "storybook/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - - "storybook/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], - - "storybook/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - - "storybook/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - - "storybook/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - - "storybook/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], - "storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "tar-fs/tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - - "tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - - "temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], - - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], - "tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tw-to-css/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -6342,18 +6279,16 @@ "tw-to-css/tailwindcss/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], - "tw-to-css/tailwindcss/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "tw-to-css/tailwindcss/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], + "vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + + "vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], - "wide-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wide-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], @@ -6428,9 +6363,7 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], - - "@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="], @@ -6440,46 +6373,36 @@ "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + "@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="], - "@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], + + "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@electron/rebuild/node-gyp/make-fetch-happen/cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], - - "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], - - "@electron/rebuild/node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - "@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -6542,23 +6465,21 @@ "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - "@npmcli/move-file/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], "@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], "@octokit/graphql/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/plugin-paginate-rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/plugin-paginate-rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/plugin-rest-endpoint-methods/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + "@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -6570,6 +6491,12 @@ "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], @@ -6590,11 +6517,11 @@ "babel-plugin-module-resolver/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - "cacache/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - "cacache/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6622,9 +6549,9 @@ "js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "node-gyp/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "opencode/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -6656,69 +6583,49 @@ "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "rimraf/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "sqlite3/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "tar-fs/tar-stream/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - - "temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "wide-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@astrojs/check/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@astrojs/check/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="], + "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="], - "@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], + "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], "@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6726,8 +6633,6 @@ "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], @@ -6740,6 +6645,10 @@ "babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "electron-builder/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -6752,64 +6661,30 @@ "js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "node-gyp/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - "rimraf/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "tar-fs/tar-stream/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], + "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/unique-filename/unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], - - "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "rimraf/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], } } diff --git a/package.json b/package.json index 530ab937c2..d1358a3966 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.16-ea816b6", "drizzle-orm": "1.0.0-beta.16-ea816b6", + "effect": "4.0.0-beta.29", "ai": "5.0.124", "hono": "4.10.7", "hono-openapi": "1.1.2", diff --git a/packages/opencode/migration/20260228203230_blue_harpoon/migration.sql b/packages/opencode/migration/20260228203230_blue_harpoon/migration.sql new file mode 100644 index 0000000000..85be58c88d --- /dev/null +++ b/packages/opencode/migration/20260228203230_blue_harpoon/migration.sql @@ -0,0 +1,17 @@ +CREATE TABLE `account` ( + `id` text PRIMARY KEY, + `email` text NOT NULL, + `url` text NOT NULL, + `access_token` text NOT NULL, + `refresh_token` text NOT NULL, + `token_expiry` integer, + `selected_org_id` text, + `time_created` integer NOT NULL, + `time_updated` integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE `account_state` ( + `id` integer PRIMARY KEY NOT NULL, + `active_account_id` text, + FOREIGN KEY (`active_account_id`) REFERENCES `account`(`id`) ON UPDATE no action ON DELETE set null +); diff --git a/packages/opencode/migration/20260228203230_blue_harpoon/snapshot.json b/packages/opencode/migration/20260228203230_blue_harpoon/snapshot.json new file mode 100644 index 0000000000..80d9451bae --- /dev/null +++ b/packages/opencode/migration/20260228203230_blue_harpoon/snapshot.json @@ -0,0 +1,1102 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "325559b7-104f-4d2a-a02c-934cfad7cfcc", + "prevIds": ["1f1dbf2d-bf66-4b25-8af4-4ba7633b7e40"], + "ddl": [ + { + "name": "account", + "entityType": "tables" + }, + { + "name": "account_state", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "selected_org_id", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_account_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "config", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": ["message_id"], + "tableTo": "message", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": ["active_account_id"], + "tableTo": "account", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_account_state_active_account_id_account_id_fk", + "entityType": "fks", + "table": "account_state" + }, + { + "columns": ["email", "url"], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": ["session_id", "position"], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "account_pk", + "table": "account", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "account_state_pk", + "table": "account_state", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": ["project_id"], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": ["session_id"], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} diff --git a/packages/opencode/migration/20260309230000_move_org_to_state/migration.sql b/packages/opencode/migration/20260309230000_move_org_to_state/migration.sql new file mode 100644 index 0000000000..4d1c7bccd0 --- /dev/null +++ b/packages/opencode/migration/20260309230000_move_org_to_state/migration.sql @@ -0,0 +1,3 @@ +ALTER TABLE `account_state` ADD `active_org_id` text;--> statement-breakpoint +UPDATE `account_state` SET `active_org_id` = (SELECT `selected_org_id` FROM `account` WHERE `account`.`id` = `account_state`.`active_account_id`);--> statement-breakpoint +ALTER TABLE `account` DROP COLUMN `selected_org_id`; diff --git a/packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json b/packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json new file mode 100644 index 0000000000..37b9eacacc --- /dev/null +++ b/packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json @@ -0,0 +1,1215 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "fb311f30-9948-4131-b15c-7d308478a878", + "prevIds": [ + "325559b7-104f-4d2a-a02c-934cfad7cfcc", + "4ec9de62-88a7-4bec-91cc-0a759e84db21" + ], + "ddl": [ + { + "name": "account_state", + "entityType": "tables" + }, + { + "name": "account", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_account_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_org_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "extra", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "columns": [ + "active_account_id" + ], + "tableTo": "account", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_account_state_active_account_id_account_id_fk", + "entityType": "fks", + "table": "account_state" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": [ + "message_id" + ], + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": [ + "email", + "url" + ], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": [ + "session_id", + "position" + ], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_state_pk", + "table": "account_state", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pk", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": [ + "project_id" + ], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "session_id" + ], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 1c24be8a19..fe991ad8ab 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -27,6 +27,7 @@ }, "devDependencies": { "@babel/core": "7.28.4", + "@effect/language-service": "0.79.0", "@octokit/webhooks-types": "7.6.1", "@opencode-ai/script": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", @@ -108,6 +109,7 @@ "decimal.js": "10.5.0", "diff": "catalog:", "drizzle-orm": "1.0.0-beta.16-ea816b6", + "effect": "catalog:", "fuzzysort": "3.1.0", "glob": "13.0.5", "google-auth-library": "10.5.0", diff --git a/packages/opencode/src/account/account.sql.ts b/packages/opencode/src/account/account.sql.ts new file mode 100644 index 0000000000..e66b3c2992 --- /dev/null +++ b/packages/opencode/src/account/account.sql.ts @@ -0,0 +1,35 @@ +import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core" +import { Timestamps } from "../storage/schema.sql" + +export const AccountTable = sqliteTable("account", { + id: text().primaryKey(), + email: text().notNull(), + url: text().notNull(), + access_token: text().notNull(), + refresh_token: text().notNull(), + token_expiry: integer(), + ...Timestamps, +}) + +export const AccountStateTable = sqliteTable("account_state", { + id: integer().primaryKey(), + active_account_id: text().references(() => AccountTable.id, { onDelete: "set null" }), + active_org_id: text(), +}) + +// LEGACY +export const ControlAccountTable = sqliteTable( + "control_account", + { + email: text().notNull(), + url: text().notNull(), + access_token: text().notNull(), + refresh_token: text().notNull(), + token_expiry: integer(), + active: integer({ mode: "boolean" }) + .notNull() + .$default(() => false), + ...Timestamps, + }, + (table) => [primaryKey({ columns: [table.email, table.url] })], +) diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts new file mode 100644 index 0000000000..b48ada1fb5 --- /dev/null +++ b/packages/opencode/src/account/index.ts @@ -0,0 +1,43 @@ +import { Effect, Option, ServiceMap } from "effect" + +import { + Account as AccountSchema, + type AccountError, + type AccessToken, + AccountID, + AccountService, + OrgID, +} from "./service" + +export { AccessToken, AccountID, OrgID } from "./service" + +import { runtime } from "@/effect/runtime" + +type AccountServiceShape = ServiceMap.Service.Shape<typeof AccountService> + +function runSync<A>(f: (service: AccountServiceShape) => Effect.Effect<A, AccountError>) { + return runtime.runSync(AccountService.use(f)) +} + +function runPromise<A>(f: (service: AccountServiceShape) => Effect.Effect<A, AccountError>) { + return runtime.runPromise(AccountService.use(f)) +} + +export namespace Account { + export const Account = AccountSchema + export type Account = AccountSchema + + export function active(): Account | undefined { + return Option.getOrUndefined(runSync((service) => service.active())) + } + + export async function config(accountID: AccountID, orgID: OrgID): Promise<Record<string, unknown> | undefined> { + const config = await runPromise((service) => service.config(accountID, orgID)) + return Option.getOrUndefined(config) + } + + export async function token(accountID: AccountID): Promise<AccessToken | undefined> { + const token = await runPromise((service) => service.token(accountID)) + return Option.getOrUndefined(token) + } +} diff --git a/packages/opencode/src/account/repo.ts b/packages/opencode/src/account/repo.ts new file mode 100644 index 0000000000..ba71abe342 --- /dev/null +++ b/packages/opencode/src/account/repo.ts @@ -0,0 +1,138 @@ +import { eq } from "drizzle-orm" +import { Effect, Layer, Option, Schema, ServiceMap } from "effect" + +import { Database } from "@/storage/db" +import { AccountStateTable, AccountTable } from "./account.sql" +import { Account, AccountID, AccountRepoError, OrgID } from "./schema" + +export type AccountRow = (typeof AccountTable)["$inferSelect"] + +const decodeAccount = Schema.decodeUnknownSync(Account) + +type DbClient = Parameters<typeof Database.use>[0] extends (db: infer T) => unknown ? T : never + +const ACCOUNT_STATE_ID = 1 + +const db = <A>(run: (db: DbClient) => A) => + Effect.try({ + try: () => Database.use(run), + catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }), + }) + +const current = (db: DbClient) => { + const state = db.select().from(AccountStateTable).where(eq(AccountStateTable.id, ACCOUNT_STATE_ID)).get() + if (!state?.active_account_id) return + const account = db.select().from(AccountTable).where(eq(AccountTable.id, state.active_account_id)).get() + if (!account) return + return { ...account, active_org_id: state.active_org_id ?? null } +} + +const setState = (db: DbClient, accountID: AccountID, orgID: string | null) => + db + .insert(AccountStateTable) + .values({ id: ACCOUNT_STATE_ID, active_account_id: accountID, active_org_id: orgID }) + .onConflictDoUpdate({ + target: AccountStateTable.id, + set: { active_account_id: accountID, active_org_id: orgID }, + }) + .run() + +export class AccountRepo extends ServiceMap.Service< + AccountRepo, + { + readonly active: () => Effect.Effect<Option.Option<Account>, AccountRepoError> + readonly list: () => Effect.Effect<Account[], AccountRepoError> + readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountRepoError> + readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountRepoError> + readonly getRow: (accountID: AccountID) => Effect.Effect<Option.Option<AccountRow>, AccountRepoError> + readonly persistToken: (input: { + accountID: AccountID + accessToken: string + refreshToken: string + expiry: Option.Option<number> + }) => Effect.Effect<void, AccountRepoError> + readonly persistAccount: (input: { + id: AccountID + email: string + url: string + accessToken: string + refreshToken: string + expiry: number + orgID: Option.Option<OrgID> + }) => Effect.Effect<void, AccountRepoError> + } +>()("@opencode/AccountRepo") { + static readonly layer: Layer.Layer<AccountRepo> = Layer.succeed( + AccountRepo, + AccountRepo.of({ + active: Effect.fn("AccountRepo.active")(() => + db((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decodeAccount(row)) : Option.none()))), + ), + + list: Effect.fn("AccountRepo.list")(() => db((db) => db.select().from(AccountTable).all().map((row) => decodeAccount({ ...row, active_org_id: null })))), + + remove: Effect.fn("AccountRepo.remove")((accountID: AccountID) => + db((db) => + Database.transaction((tx) => { + tx.update(AccountStateTable) + .set({ active_account_id: null, active_org_id: null }) + .where(eq(AccountStateTable.active_account_id, accountID)) + .run() + tx.delete(AccountTable).where(eq(AccountTable.id, accountID)).run() + }), + ).pipe(Effect.asVoid), + ), + + use: Effect.fn("AccountRepo.use")((accountID: AccountID, orgID: Option.Option<OrgID>) => + db((db) => setState(db, accountID, Option.getOrNull(orgID))).pipe(Effect.asVoid), + ), + + getRow: Effect.fn("AccountRepo.getRow")((accountID: AccountID) => + db((db) => db.select().from(AccountTable).where(eq(AccountTable.id, accountID)).get()).pipe( + Effect.map(Option.fromNullishOr), + ), + ), + + persistToken: Effect.fn("AccountRepo.persistToken")((input) => + db((db) => + db + .update(AccountTable) + .set({ + access_token: input.accessToken, + refresh_token: input.refreshToken, + token_expiry: Option.getOrNull(input.expiry), + }) + .where(eq(AccountTable.id, input.accountID)) + .run(), + ).pipe(Effect.asVoid), + ), + + persistAccount: Effect.fn("AccountRepo.persistAccount")((input) => { + const orgID = Option.getOrNull(input.orgID) + return db((db) => + Database.transaction((tx) => { + tx.insert(AccountTable) + .values({ + id: input.id, + email: input.email, + url: input.url, + access_token: input.accessToken, + refresh_token: input.refreshToken, + token_expiry: input.expiry, + }) + .onConflictDoUpdate({ + target: AccountTable.id, + set: { + access_token: input.accessToken, + refresh_token: input.refreshToken, + token_expiry: input.expiry, + }, + }) + .run() + setState(tx, input.id, orgID) + }), + ).pipe(Effect.asVoid) + }), + }), + ) +} diff --git a/packages/opencode/src/account/schema.ts b/packages/opencode/src/account/schema.ts new file mode 100644 index 0000000000..49c37932ff --- /dev/null +++ b/packages/opencode/src/account/schema.ts @@ -0,0 +1,73 @@ +import { Schema } from "effect" + +import { withStatics } from "@/util/schema" + +export const AccountID = Schema.String.pipe( + Schema.brand("AccountId"), + withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })), +) +export type AccountID = Schema.Schema.Type<typeof AccountID> + +export const OrgID = Schema.String.pipe( + Schema.brand("OrgId"), + withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })), +) +export type OrgID = Schema.Schema.Type<typeof OrgID> + +export const AccessToken = Schema.String.pipe( + Schema.brand("AccessToken"), + withStatics((s) => ({ make: (token: string) => s.makeUnsafe(token) })), +) +export type AccessToken = Schema.Schema.Type<typeof AccessToken> + +export class Account extends Schema.Class<Account>("Account")({ + id: AccountID, + email: Schema.String, + url: Schema.String, + active_org_id: Schema.NullOr(OrgID), +}) {} + +export class Org extends Schema.Class<Org>("Org")({ + id: OrgID, + name: Schema.String, +}) {} + +export class AccountRepoError extends Schema.TaggedErrorClass<AccountRepoError>()("AccountRepoError", { + message: Schema.String, + cause: Schema.optional(Schema.Defect), +}) {} + +export class AccountServiceError extends Schema.TaggedErrorClass<AccountServiceError>()("AccountServiceError", { + message: Schema.String, + cause: Schema.optional(Schema.Defect), +}) {} + +export type AccountError = AccountRepoError | AccountServiceError + +export class Login extends Schema.Class<Login>("Login")({ + code: Schema.String, + user: Schema.String, + url: Schema.String, + server: Schema.String, + expiry: Schema.Number, + interval: Schema.Number, +}) {} + +export class PollSuccess extends Schema.TaggedClass<PollSuccess>()("PollSuccess", { + email: Schema.String, +}) {} + +export class PollPending extends Schema.TaggedClass<PollPending>()("PollPending", {}) {} + +export class PollSlow extends Schema.TaggedClass<PollSlow>()("PollSlow", {}) {} + +export class PollExpired extends Schema.TaggedClass<PollExpired>()("PollExpired", {}) {} + +export class PollDenied extends Schema.TaggedClass<PollDenied>()("PollDenied", {}) {} + +export class PollError extends Schema.TaggedClass<PollError>()("PollError", { + cause: Schema.Defect, +}) {} + +export const PollResult = Schema.Union([PollSuccess, PollPending, PollSlow, PollExpired, PollDenied, PollError]) +export type PollResult = Schema.Schema.Type<typeof PollResult> diff --git a/packages/opencode/src/account/service.ts b/packages/opencode/src/account/service.ts new file mode 100644 index 0000000000..68ea9dbdec --- /dev/null +++ b/packages/opencode/src/account/service.ts @@ -0,0 +1,384 @@ +import { Clock, Effect, Layer, Option, Schema, ServiceMap } from "effect" +import { + FetchHttpClient, + HttpClient, + HttpClientError, + HttpClientRequest, + HttpClientResponse, +} from "effect/unstable/http" + +import { withTransientReadRetry } from "@/util/effect-http-client" +import { AccountRepo, type AccountRow } from "./repo" +import { + type AccountError, + AccessToken, + Account, + AccountID, + AccountServiceError, + Login, + Org, + OrgID, + PollDenied, + PollError, + PollExpired, + PollPending, + type PollResult, + PollSlow, + PollSuccess, +} from "./schema" + +export * from "./schema" + +export type AccountOrgs = { + account: Account + orgs: Org[] +} + +const RemoteOrg = Schema.Struct({ + id: Schema.optional(OrgID), + name: Schema.optional(Schema.String), +}) + +const RemoteOrgs = Schema.Array(RemoteOrg) + +const RemoteConfig = Schema.Struct({ + config: Schema.Record(Schema.String, Schema.Json), +}) + +const TokenRefresh = Schema.Struct({ + access_token: Schema.String, + refresh_token: Schema.optional(Schema.String), + expires_in: Schema.optional(Schema.Number), +}) + +const DeviceCode = Schema.Struct({ + device_code: Schema.String, + user_code: Schema.String, + verification_uri_complete: Schema.String, + expires_in: Schema.Number, + interval: Schema.Number, +}) + +const DeviceToken = Schema.Struct({ + access_token: Schema.optional(Schema.String), + refresh_token: Schema.optional(Schema.String), + expires_in: Schema.optional(Schema.Number), + error: Schema.optional(Schema.String), + error_description: Schema.optional(Schema.String), +}) + +const User = Schema.Struct({ + id: Schema.optional(AccountID), + email: Schema.optional(Schema.String), +}) + +const ClientId = Schema.Struct({ client_id: Schema.String }) + +const DeviceTokenRequest = Schema.Struct({ + grant_type: Schema.String, + device_code: Schema.String, + client_id: Schema.String, +}) + +const clientId = "opencode-cli" + +const toAccountServiceError = (message: string, cause?: unknown) => new AccountServiceError({ message, cause }) + +const mapAccountServiceError = + (operation: string, message = "Account service operation failed") => + <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, AccountServiceError, R> => + effect.pipe( + Effect.mapError((error) => + error instanceof AccountServiceError ? error : toAccountServiceError(`${message} (${operation})`, error), + ), + ) + +export class AccountService extends ServiceMap.Service< + AccountService, + { + readonly active: () => Effect.Effect<Option.Option<Account>, AccountError> + readonly list: () => Effect.Effect<Account[], AccountError> + readonly orgsByAccount: () => Effect.Effect<AccountOrgs[], AccountError> + readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountError> + readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountError> + readonly orgs: (accountID: AccountID) => Effect.Effect<Org[], AccountError> + readonly config: ( + accountID: AccountID, + orgID: OrgID, + ) => Effect.Effect<Option.Option<Record<string, unknown>>, AccountError> + readonly token: (accountID: AccountID) => Effect.Effect<Option.Option<AccessToken>, AccountError> + readonly login: (url: string) => Effect.Effect<Login, AccountError> + readonly poll: (input: Login) => Effect.Effect<PollResult, AccountError> + } +>()("@opencode/Account") { + static readonly layer: Layer.Layer<AccountService, never, AccountRepo | HttpClient.HttpClient> = Layer.effect( + AccountService, + Effect.gen(function* () { + const repo = yield* AccountRepo + const http = yield* HttpClient.HttpClient + const httpRead = withTransientReadRetry(http) + + const execute = (operation: string, request: HttpClientRequest.HttpClientRequest) => + http.execute(request).pipe(mapAccountServiceError(operation, "HTTP request failed")) + + const executeRead = (operation: string, request: HttpClientRequest.HttpClientRequest) => + httpRead.execute(request).pipe(mapAccountServiceError(operation, "HTTP request failed")) + + const executeEffect = <E>(operation: string, request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) => + request.pipe( + Effect.flatMap((req) => http.execute(req)), + mapAccountServiceError(operation, "HTTP request failed"), + ) + + const okOrNone = (operation: string, response: HttpClientResponse.HttpClientResponse) => + HttpClientResponse.filterStatusOk(response).pipe( + Effect.map(Option.some), + Effect.catch((error) => + HttpClientError.isHttpClientError(error) && error.reason._tag === "StatusCodeError" + ? Effect.succeed(Option.none<HttpClientResponse.HttpClientResponse>()) + : Effect.fail(error), + ), + mapAccountServiceError(operation), + ) + + const tokenForRow = Effect.fn("AccountService.tokenForRow")(function* (found: AccountRow) { + const now = yield* Clock.currentTimeMillis + if (found.token_expiry && found.token_expiry > now) return Option.some(AccessToken.make(found.access_token)) + + const response = yield* execute( + "token.refresh", + HttpClientRequest.post(`${found.url}/oauth/token`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.bodyUrlParams({ + grant_type: "refresh_token", + refresh_token: found.refresh_token, + }), + ), + ) + + const ok = yield* okOrNone("token.refresh", response) + if (Option.isNone(ok)) return Option.none() + + const parsed = yield* HttpClientResponse.schemaBodyJson(TokenRefresh)(ok.value).pipe( + mapAccountServiceError("token.refresh", "Failed to decode response"), + ) + + const expiry = Option.fromNullishOr(parsed.expires_in).pipe(Option.map((e) => now + e * 1000)) + + yield* repo.persistToken({ + accountID: AccountID.make(found.id), + accessToken: parsed.access_token, + refreshToken: parsed.refresh_token ?? found.refresh_token, + expiry, + }) + + return Option.some(AccessToken.make(parsed.access_token)) + }) + + const resolveAccess = Effect.fn("AccountService.resolveAccess")(function* (accountID: AccountID) { + const maybeAccount = yield* repo.getRow(accountID) + if (Option.isNone(maybeAccount)) return Option.none<{ account: AccountRow; accessToken: AccessToken }>() + + const account = maybeAccount.value + const accessToken = yield* tokenForRow(account) + if (Option.isNone(accessToken)) return Option.none<{ account: AccountRow; accessToken: AccessToken }>() + + return Option.some({ account, accessToken: accessToken.value }) + }) + + const token = Effect.fn("AccountService.token")((accountID: AccountID) => + resolveAccess(accountID).pipe(Effect.map(Option.map((r) => r.accessToken))), + ) + + const orgsByAccount = Effect.fn("AccountService.orgsByAccount")(function* () { + const accounts = yield* repo.list() + return yield* Effect.forEach( + accounts, + (account) => orgs(account.id).pipe(Effect.map((orgs) => ({ account, orgs }))), + { concurrency: 3 }, + ) + }) + + const orgs = Effect.fn("AccountService.orgs")(function* (accountID: AccountID) { + const resolved = yield* resolveAccess(accountID) + if (Option.isNone(resolved)) return [] + + const { account, accessToken } = resolved.value + + const response = yield* executeRead( + "orgs", + HttpClientRequest.get(`${account.url}/api/orgs`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.bearerToken(accessToken), + ), + ) + + const ok = yield* okOrNone("orgs", response) + if (Option.isNone(ok)) return [] + + const orgs = yield* HttpClientResponse.schemaBodyJson(RemoteOrgs)(ok.value).pipe( + mapAccountServiceError("orgs", "Failed to decode response"), + ) + return orgs + .filter((org) => org.id !== undefined && org.name !== undefined) + .map((org) => new Org({ id: org.id!, name: org.name! })) + }) + + const config = Effect.fn("AccountService.config")(function* (accountID: AccountID, orgID: OrgID) { + const resolved = yield* resolveAccess(accountID) + if (Option.isNone(resolved)) return Option.none() + + const { account, accessToken } = resolved.value + + const response = yield* executeRead( + "config", + HttpClientRequest.get(`${account.url}/api/config`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.bearerToken(accessToken), + HttpClientRequest.setHeaders({ "x-org-id": orgID }), + ), + ) + + const ok = yield* okOrNone("config", response) + if (Option.isNone(ok)) return Option.none() + + const parsed = yield* HttpClientResponse.schemaBodyJson(RemoteConfig)(ok.value).pipe( + mapAccountServiceError("config", "Failed to decode response"), + ) + return Option.some(parsed.config) + }) + + const login = Effect.fn("AccountService.login")(function* (server: string) { + const response = yield* executeEffect( + "login", + HttpClientRequest.post(`${server}/auth/device/code`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.schemaBodyJson(ClientId)({ client_id: clientId }), + ), + ) + + const ok = yield* okOrNone("login", response) + if (Option.isNone(ok)) { + const body = yield* response.text.pipe(Effect.orElseSucceed(() => "")) + return yield* toAccountServiceError(`Failed to initiate device flow: ${body || response.status}`) + } + + const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceCode)(ok.value).pipe( + mapAccountServiceError("login", "Failed to decode response"), + ) + return new Login({ + code: parsed.device_code, + user: parsed.user_code, + url: `${server}${parsed.verification_uri_complete}`, + server, + expiry: parsed.expires_in, + interval: parsed.interval, + }) + }) + + const poll = Effect.fn("AccountService.poll")(function* (input: Login) { + const response = yield* executeEffect( + "poll", + HttpClientRequest.post(`${input.server}/auth/device/token`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.schemaBodyJson(DeviceTokenRequest)({ + grant_type: "urn:ietf:params:oauth:grant-type:device_code", + device_code: input.code, + client_id: clientId, + }), + ), + ) + + const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceToken)(response).pipe( + mapAccountServiceError("poll", "Failed to decode response"), + ) + + if (!parsed.access_token) { + if (parsed.error === "authorization_pending") return new PollPending() + if (parsed.error === "slow_down") return new PollSlow() + if (parsed.error === "expired_token") return new PollExpired() + if (parsed.error === "access_denied") return new PollDenied() + return new PollError({ cause: parsed.error }) + } + + const access = parsed.access_token + + const fetchUser = executeRead( + "poll.user", + HttpClientRequest.get(`${input.server}/api/user`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.bearerToken(access), + ), + ).pipe( + Effect.flatMap((r) => + HttpClientResponse.schemaBodyJson(User)(r).pipe( + mapAccountServiceError("poll.user", "Failed to decode response"), + ), + ), + ) + + const fetchOrgs = executeRead( + "poll.orgs", + HttpClientRequest.get(`${input.server}/api/orgs`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.bearerToken(access), + ), + ).pipe( + Effect.flatMap((r) => + HttpClientResponse.schemaBodyJson(RemoteOrgs)(r).pipe( + mapAccountServiceError("poll.orgs", "Failed to decode response"), + ), + ), + ) + + const [user, remoteOrgs] = yield* Effect.all([fetchUser, fetchOrgs], { concurrency: 2 }) + + const userId = user.id + const userEmail = user.email + + if (!userId || !userEmail) { + return new PollError({ cause: "No id or email in response" }) + } + + const firstOrgID = remoteOrgs.length > 0 ? Option.fromNullishOr(remoteOrgs[0].id) : Option.none() + + const now = yield* Clock.currentTimeMillis + const expiry = now + (parsed.expires_in ?? 0) * 1000 + const refresh = parsed.refresh_token ?? "" + if (!refresh) { + yield* Effect.logWarning("Server did not return a refresh token — session may expire without ability to refresh") + } + + yield* repo.persistAccount({ + id: userId, + email: userEmail, + url: input.server, + accessToken: access, + refreshToken: refresh, + expiry, + orgID: firstOrgID, + }) + + return new PollSuccess({ email: userEmail }) + }) + + return AccountService.of({ + active: repo.active, + list: repo.list, + orgsByAccount, + remove: repo.remove, + use: repo.use, + orgs, + config, + token, + login, + poll, + }) + }), + ) + + static readonly defaultLayer = AccountService.layer.pipe( + Layer.provide(AccountRepo.layer), + Layer.provide(FetchHttpClient.layer), + ) +} diff --git a/packages/opencode/src/cli/cmd/account.ts b/packages/opencode/src/cli/cmd/account.ts new file mode 100644 index 0000000000..51cf2b138c --- /dev/null +++ b/packages/opencode/src/cli/cmd/account.ts @@ -0,0 +1,196 @@ +import { cmd } from "./cmd" +import { Duration, Effect, Match, Option } from "effect" +import { UI } from "../ui" +import { runtime } from "@/effect/runtime" +import { AccountID, AccountService, OrgID, PollExpired, type PollResult } from "@/account/service" +import { type AccountError } from "@/account/schema" +import * as Prompt from "../effect/prompt" +import open from "open" + +const openBrowser = (url: string) => Effect.promise(() => open(url).catch(() => undefined)) + +const println = (msg: string) => Effect.sync(() => UI.println(msg)) + +const loginEffect = Effect.fn("login")(function* (url: string) { + const service = yield* AccountService + + yield* Prompt.intro("Log in") + const login = yield* service.login(url) + + yield* Prompt.log.info("Go to: " + login.url) + yield* Prompt.log.info("Enter code: " + login.user) + yield* openBrowser(login.url) + + const s = Prompt.spinner() + yield* s.start("Waiting for authorization...") + + const poll = (wait: number): Effect.Effect<PollResult, AccountError> => + Effect.gen(function* () { + yield* Effect.sleep(wait) + const result = yield* service.poll(login) + if (result._tag === "PollPending") return yield* poll(wait) + if (result._tag === "PollSlow") return yield* poll(wait + 5000) + return result + }) + + const result = yield* poll(login.interval * 1000).pipe( + Effect.timeout(Duration.seconds(login.expiry)), + Effect.catchTag("TimeoutError", () => Effect.succeed(new PollExpired())), + ) + + yield* Match.valueTags(result, { + PollSuccess: (r) => + Effect.gen(function* () { + yield* s.stop("Logged in as " + r.email) + yield* Prompt.outro("Done") + }), + PollExpired: () => s.stop("Device code expired", 1), + PollDenied: () => s.stop("Authorization denied", 1), + PollError: (r) => s.stop("Error: " + String(r.cause), 1), + PollPending: () => s.stop("Unexpected state", 1), + PollSlow: () => s.stop("Unexpected state", 1), + }) +}) + +const logoutEffect = Effect.fn("logout")(function* (email?: string) { + const service = yield* AccountService + const accounts = yield* service.list() + if (accounts.length === 0) return yield* println("Not logged in") + + if (email) { + const match = accounts.find((a) => a.email === email) + if (!match) return yield* println("Account not found: " + email) + yield* service.remove(match.id) + yield* Prompt.outro("Logged out from " + email) + return + } + + const active = yield* service.active() + const activeID = Option.map(active, (a) => a.id) + + yield* Prompt.intro("Log out") + + const opts = accounts.map((a) => { + const isActive = Option.isSome(activeID) && activeID.value === a.id + const server = UI.Style.TEXT_DIM + a.url + UI.Style.TEXT_NORMAL + return { + value: a, + label: isActive + ? `${a.email} ${server}` + UI.Style.TEXT_DIM + " (active)" + : `${a.email} ${server}`, + } + }) + + const selected = yield* Prompt.select({ message: "Select account to log out", options: opts }) + if (Option.isNone(selected)) return + + yield* service.remove(selected.value.id) + yield* Prompt.outro("Logged out from " + selected.value.email) +}) + +interface OrgChoice { + orgID: OrgID + accountID: AccountID + label: string +} + +const switchEffect = Effect.fn("switch")(function* () { + const service = yield* AccountService + + const groups = yield* service.orgsByAccount() + if (groups.length === 0) return yield* println("Not logged in") + + const active = yield* service.active() + const activeOrgID = Option.flatMap(active, (a) => Option.fromNullishOr(a.active_org_id)) + + const opts = groups.flatMap((group) => + group.orgs.map((org) => { + const isActive = Option.isSome(activeOrgID) && activeOrgID.value === org.id + return { + value: { orgID: org.id, accountID: group.account.id, label: org.name }, + label: isActive + ? `${org.name} (${group.account.email})` + UI.Style.TEXT_DIM + " (active)" + : `${org.name} (${group.account.email})`, + } + }), + ) + if (opts.length === 0) return yield* println("No orgs found") + + yield* Prompt.intro("Switch org") + + const selected = yield* Prompt.select<OrgChoice>({ message: "Select org", options: opts }) + if (Option.isNone(selected)) return + + const choice = selected.value + yield* service.use(choice.accountID, Option.some(choice.orgID)) + yield* Prompt.outro("Switched to " + choice.label) +}) + +const orgsEffect = Effect.fn("orgs")(function* () { + const service = yield* AccountService + + const groups = yield* service.orgsByAccount() + if (groups.length === 0) return yield* println("No accounts found") + if (!groups.some((group) => group.orgs.length > 0)) return yield* println("No orgs found") + + const active = yield* service.active() + const activeOrgID = Option.flatMap(active, (a) => Option.fromNullishOr(a.active_org_id)) + + for (const group of groups) { + for (const org of group.orgs) { + const isActive = Option.isSome(activeOrgID) && activeOrgID.value === org.id + const dot = isActive ? UI.Style.TEXT_SUCCESS + "●" + UI.Style.TEXT_NORMAL : " " + const name = isActive ? UI.Style.TEXT_HIGHLIGHT_BOLD + org.name + UI.Style.TEXT_NORMAL : org.name + const email = UI.Style.TEXT_DIM + group.account.email + UI.Style.TEXT_NORMAL + const id = UI.Style.TEXT_DIM + org.id + UI.Style.TEXT_NORMAL + yield* println(` ${dot} ${name} ${email} ${id}`) + } + } +}) + +export const LoginCommand = cmd({ + command: "login <url>", + describe: false, + builder: (yargs) => + yargs.positional("url", { + describe: "server URL", + type: "string", + demandOption: true, + }), + async handler(args) { + UI.empty() + await runtime.runPromise(loginEffect(args.url)) + }, +}) + +export const LogoutCommand = cmd({ + command: "logout [email]", + describe: false, + builder: (yargs) => + yargs.positional("email", { + describe: "account email to log out from", + type: "string", + }), + async handler(args) { + UI.empty() + await runtime.runPromise(logoutEffect(args.email)) + }, +}) + +export const SwitchCommand = cmd({ + command: "switch", + describe: false, + async handler() { + UI.empty() + await runtime.runPromise(switchEffect()) + }, +}) + +export const OrgsCommand = cmd({ + command: "orgs", + describe: false, + async handler() { + UI.empty() + await runtime.runPromise(orgsEffect()) + }, +}) diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index 58c1928256..eb5964379a 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -10,7 +10,7 @@ import { ShareNext } from "../../share/share-next" import { EOL } from "os" import { Filesystem } from "../../util/filesystem" -/** Discriminated union returned by the ShareNext API (GET /api/share/:id/data) */ +/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */ export type ShareData = | { type: "session"; data: SDKSession } | { type: "message"; data: Message } @@ -24,6 +24,14 @@ export function parseShareUrl(url: string): string | null { return match ? match[1] : null } +export function shouldAttachShareAuthHeaders(shareUrl: string, accountBaseUrl: string): boolean { + try { + return new URL(shareUrl).origin === new URL(accountBaseUrl).origin + } catch { + return false + } +} + /** * Transform ShareNext API response (flat array) into the nested structure for local file storage. * @@ -97,8 +105,21 @@ export const ImportCommand = cmd({ return } - const baseUrl = await ShareNext.url() - const response = await fetch(`${baseUrl}/api/share/${slug}/data`) + const parsed = new URL(args.file) + const baseUrl = parsed.origin + const req = await ShareNext.request() + const headers = shouldAttachShareAuthHeaders(args.file, req.baseUrl) ? req.headers : {} + + const dataPath = req.api.data(slug) + let response = await fetch(`${baseUrl}${dataPath}`, { + headers, + }) + + if (!response.ok && dataPath !== `/api/share/${slug}/data`) { + response = await fetch(`${baseUrl}/api/share/${slug}/data`, { + headers, + }) + } if (!response.ok) { process.stdout.write(`Failed to fetch share data: ${response.statusText}`) diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/providers.ts similarity index 92% rename from packages/opencode/src/cli/cmd/auth.ts rename to packages/opencode/src/cli/cmd/providers.ts index 38fba0ce70..53a9b1280e 100644 --- a/packages/opencode/src/cli/cmd/auth.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -13,14 +13,9 @@ import { Instance } from "../../project/instance" import type { Hooks } from "@opencode-ai/plugin" import { Process } from "../../util/process" import { text } from "node:stream/consumers" -import { setTimeout as sleep } from "node:timers/promises" type PluginAuth = NonNullable<Hooks["auth"]> -/** - * Handle plugin-based authentication flow. - * Returns true if auth was handled, false if it should fall through to default handling. - */ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string, methodName?: string): Promise<boolean> { let index = 0 if (methodName) { @@ -33,7 +28,7 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string, } index = match } else if (plugin.auth.methods.length > 1) { - const selected = await prompts.select({ + const method = await prompts.select({ message: "Login method", options: [ ...plugin.auth.methods.map((x, index) => ({ @@ -42,13 +37,12 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string, })), ], }) - if (prompts.isCancel(selected)) throw new UI.CancelledError() - index = parseInt(selected) + if (prompts.isCancel(method)) throw new UI.CancelledError() + index = parseInt(method) } const method = plugin.auth.methods[index] - // Handle prompts for all auth types - await sleep(10) + await new Promise((r) => setTimeout(r, 10)) const inputs: Record<string, string> = {} if (method.prompts) { for (const prompt of method.prompts) { @@ -171,11 +165,6 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string, return false } -/** - * Build a deduplicated list of plugin-registered auth providers that are not - * already present in models.dev, respecting enabled/disabled provider lists. - * Pure function with no side effects; safe to test without mocking. - */ export function resolvePluginProviders(input: { hooks: Hooks[] existingProviders: Record<string, unknown> @@ -203,19 +192,20 @@ export function resolvePluginProviders(input: { return result } -export const AuthCommand = cmd({ - command: "auth", - describe: "manage credentials", +export const ProvidersCommand = cmd({ + command: "providers", + aliases: ["auth"], + describe: "manage AI providers and credentials", builder: (yargs) => - yargs.command(AuthLoginCommand).command(AuthLogoutCommand).command(AuthListCommand).demandCommand(), + yargs.command(ProvidersListCommand).command(ProvidersLoginCommand).command(ProvidersLogoutCommand).demandCommand(), async handler() {}, }) -export const AuthListCommand = cmd({ +export const ProvidersListCommand = cmd({ command: "list", aliases: ["ls"], - describe: "list providers", - async handler() { + describe: "list providers and credentials", + async handler(_args) { UI.empty() const authPath = path.join(Global.Path.data, "auth.json") const homedir = os.homedir() @@ -231,7 +221,6 @@ export const AuthListCommand = cmd({ prompts.outro(`${results.length} credentials`) - // Environment variables section const activeEnvVars: Array<{ provider: string; envVar: string }> = [] for (const [providerID, provider] of Object.entries(database)) { @@ -258,7 +247,7 @@ export const AuthListCommand = cmd({ }, }) -export const AuthLoginCommand = cmd({ +export const ProvidersLoginCommand = cmd({ command: "login [url]", describe: "log in to a provider", builder: (yargs) => @@ -356,7 +345,7 @@ export const AuthLoginCommand = cmd({ value: x.id, hint: { opencode: "recommended", - anthropic: "Claude Max or API key", + anthropic: "API key", openai: "ChatGPT Plus/Pro or API key", }[x.id], })), @@ -409,7 +398,6 @@ export const AuthLoginCommand = cmd({ if (prompts.isCancel(custom)) throw new UI.CancelledError() provider = custom.replace(/^@ai-sdk\//, "") - // Check if a plugin provides auth for this custom provider const customPlugin = await Plugin.list().then((x) => x.findLast((x) => x.auth?.provider === provider)) if (customPlugin && customPlugin.auth) { const handled = await handlePluginAuth({ auth: customPlugin.auth }, provider, args.method) @@ -461,10 +449,10 @@ export const AuthLoginCommand = cmd({ }, }) -export const AuthLogoutCommand = cmd({ +export const ProvidersLogoutCommand = cmd({ command: "logout", describe: "log out from a configured provider", - async handler() { + async handler(_args) { UI.empty() const credentials = await Auth.all().then((x) => Object.entries(x)) prompts.intro("Remove credential") diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index 7bf189f090..f77e4727aa 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -36,7 +36,7 @@ export function createDialogProviderOptions() { value: provider.id, description: { opencode: "(Recommended)", - anthropic: "(Claude Max or API key)", + anthropic: "(API key)", openai: "(ChatGPT Plus/Pro or API key)", "opencode-go": "Low cost subscription for everyone", }[provider.id], diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 5358b61ef3..7456742cdf 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -383,7 +383,12 @@ export function Session() { sessionID: route.sessionID, }) .then((res) => copy(res.data!.share!.url)) - .catch(() => toast.show({ message: "Failed to share session", variant: "error" })) + .catch((error) => { + toast.show({ + message: error instanceof Error ? error.message : "Failed to share session", + variant: "error", + }) + }) dialog.clear() }, }, @@ -486,7 +491,12 @@ export function Session() { sessionID: route.sessionID, }) .then(() => toast.show({ message: "Session unshared successfully", variant: "success" })) - .catch(() => toast.show({ message: "Failed to unshare session", variant: "error" })) + .catch((error) => { + toast.show({ + message: error instanceof Error ? error.message : "Failed to unshare session", + variant: "error", + }) + }) dialog.clear() }, }, diff --git a/packages/opencode/src/cli/effect/prompt.ts b/packages/opencode/src/cli/effect/prompt.ts new file mode 100644 index 0000000000..7f9cd8cfe6 --- /dev/null +++ b/packages/opencode/src/cli/effect/prompt.ts @@ -0,0 +1,25 @@ +import * as prompts from "@clack/prompts" +import { Effect, Option } from "effect" + +export const intro = (msg: string) => Effect.sync(() => prompts.intro(msg)) +export const outro = (msg: string) => Effect.sync(() => prompts.outro(msg)) + +export const log = { + info: (msg: string) => Effect.sync(() => prompts.log.info(msg)), +} + +export const select = <Value>(opts: Parameters<typeof prompts.select<Value>>[0]) => + Effect.tryPromise(() => prompts.select(opts)).pipe( + Effect.map((result) => { + if (prompts.isCancel(result)) return Option.none<Value>() + return Option.some(result) + }), + ) + +export const spinner = () => { + const s = prompts.spinner() + return { + start: (msg: string) => Effect.sync(() => s.start(msg)), + stop: (msg: string, code?: number) => Effect.sync(() => s.stop(msg, code)), + } +} diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ef2821727b..2b8aa9e030 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -12,6 +12,7 @@ import { lazy } from "../util/lazy" import { NamedError } from "@opencode-ai/util/error" import { Flag } from "../flag/flag" import { Auth } from "../auth" +import { Env } from "../env" import { type ParseError as JsoncParseError, applyEdits, @@ -32,7 +33,7 @@ import { Glob } from "../util/glob" import { PackageRegistry } from "@/bun/registry" import { proxied } from "@/util/proxied" import { iife } from "@/util/iife" -import { Control } from "@/control" +import { Account } from "@/account" import { ConfigPaths } from "./paths" import { Filesystem } from "@/util/filesystem" @@ -108,10 +109,6 @@ export namespace Config { } } - const token = await Control.token() - if (token) { - } - // Global user config overrides remote config. result = mergeConfigConcatArrays(result, await global()) @@ -178,6 +175,32 @@ export namespace Config { log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT") } + const active = Account.active() + if (active?.active_org_id) { + try { + const [config, token] = await Promise.all([ + Account.config(active.id, active.active_org_id), + Account.token(active.id), + ]) + if (token) { + process.env["OPENCODE_CONSOLE_TOKEN"] = token + Env.set("OPENCODE_CONSOLE_TOKEN", token) + } + + if (config) { + result = mergeConfigConcatArrays( + result, + await load(JSON.stringify(config), { + dir: path.dirname(`${active.url}/api/config`), + source: `${active.url}/api/config`, + }), + ) + } + } catch (err: any) { + log.debug("failed to fetch remote account config", { error: err?.message ?? err }) + } + } + // Load managed config files last (highest priority) - enterprise admin-controlled // Kept separate from directories array to avoid write operations when installing plugins // which would fail on system directories requiring elevated permissions diff --git a/packages/opencode/src/control-plane/workspace.sql.ts b/packages/opencode/src/control-plane/workspace.sql.ts index 1ba1605f8e..7639620691 100644 --- a/packages/opencode/src/control-plane/workspace.sql.ts +++ b/packages/opencode/src/control-plane/workspace.sql.ts @@ -1,5 +1,5 @@ import { sqliteTable, text } from "drizzle-orm/sqlite-core" -import { ProjectTable } from "@/project/project.sql" +import { ProjectTable } from "../project/project.sql" export const WorkspaceTable = sqliteTable("workspace", { id: text().primaryKey(), diff --git a/packages/opencode/src/control/control.sql.ts b/packages/opencode/src/control/control.sql.ts deleted file mode 100644 index 7b805c1627..0000000000 --- a/packages/opencode/src/control/control.sql.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { sqliteTable, text, integer, primaryKey, uniqueIndex } from "drizzle-orm/sqlite-core" -import { eq } from "drizzle-orm" -import { Timestamps } from "@/storage/schema.sql" - -export const ControlAccountTable = sqliteTable( - "control_account", - { - email: text().notNull(), - url: text().notNull(), - access_token: text().notNull(), - refresh_token: text().notNull(), - token_expiry: integer(), - active: integer({ mode: "boolean" }) - .notNull() - .$default(() => false), - ...Timestamps, - }, - (table) => [ - primaryKey({ columns: [table.email, table.url] }), - // uniqueIndex("control_account_active_idx").on(table.email).where(eq(table.active, true)), - ], -) diff --git a/packages/opencode/src/control/index.ts b/packages/opencode/src/control/index.ts deleted file mode 100644 index f712e88281..0000000000 --- a/packages/opencode/src/control/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { eq, and } from "drizzle-orm" -import { Database } from "@/storage/db" -import { ControlAccountTable } from "./control.sql" -import z from "zod" - -export * from "./control.sql" - -export namespace Control { - export const Account = z.object({ - email: z.string(), - url: z.string(), - }) - export type Account = z.infer<typeof Account> - - function fromRow(row: (typeof ControlAccountTable)["$inferSelect"]): Account { - return { - email: row.email, - url: row.url, - } - } - - export function account(): Account | undefined { - const row = Database.use((db) => - db.select().from(ControlAccountTable).where(eq(ControlAccountTable.active, true)).get(), - ) - return row ? fromRow(row) : undefined - } - - export async function token(): Promise<string | undefined> { - const row = Database.use((db) => - db.select().from(ControlAccountTable).where(eq(ControlAccountTable.active, true)).get(), - ) - if (!row) return undefined - if (row.token_expiry && row.token_expiry > Date.now()) return row.access_token - - const res = await fetch(`${row.url}/oauth/token`, { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - grant_type: "refresh_token", - refresh_token: row.refresh_token, - }).toString(), - }) - - if (!res.ok) return - - const json = (await res.json()) as { - access_token: string - refresh_token?: string - expires_in?: number - } - - Database.use((db) => - db - .update(ControlAccountTable) - .set({ - access_token: json.access_token, - refresh_token: json.refresh_token ?? row.refresh_token, - token_expiry: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined, - }) - .where(and(eq(ControlAccountTable.email, row.email), eq(ControlAccountTable.url, row.url))) - .run(), - ) - - return json.access_token - } -} diff --git a/packages/opencode/src/effect/runtime.ts b/packages/opencode/src/effect/runtime.ts new file mode 100644 index 0000000000..1868b38a01 --- /dev/null +++ b/packages/opencode/src/effect/runtime.ts @@ -0,0 +1,4 @@ +import { ManagedRuntime } from "effect" +import { AccountService } from "@/account/service" + +export const runtime = ManagedRuntime.make(AccountService.defaultLayer) diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 4fd5f0e67b..10ed8530ac 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -3,7 +3,8 @@ import { hideBin } from "yargs/helpers" import { RunCommand } from "./cli/cmd/run" import { GenerateCommand } from "./cli/cmd/generate" import { Log } from "./util/log" -import { AuthCommand } from "./cli/cmd/auth" +import { LoginCommand, LogoutCommand, SwitchCommand, OrgsCommand } from "./cli/cmd/account" +import { ProvidersCommand } from "./cli/cmd/providers" import { AgentCommand } from "./cli/cmd/agent" import { UpgradeCommand } from "./cli/cmd/upgrade" import { UninstallCommand } from "./cli/cmd/uninstall" @@ -134,7 +135,11 @@ let cli = yargs(hideBin(process.argv)) .command(RunCommand) .command(GenerateCommand) .command(DebugCommand) - .command(AuthCommand) + .command(LoginCommand) + .command(LogoutCommand) + .command(SwitchCommand) + .command(OrgsCommand) + .command(ProvidersCommand) .command(AgentCommand) .command(UpgradeCommand) .command(UninstallCommand) diff --git a/packages/opencode/src/project/project.sql.ts b/packages/opencode/src/project/project.sql.ts index 12373244f5..7f0f8ca532 100644 --- a/packages/opencode/src/project/project.sql.ts +++ b/packages/opencode/src/project/project.sql.ts @@ -1,5 +1,5 @@ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core" -import { Timestamps } from "@/storage/schema.sql" +import { Timestamps } from "../storage/schema.sql" export const ProjectTable = sqliteTable("project", { id: text().primaryKey(), diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 0630760f3b..b3228f400f 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -1,9 +1,9 @@ import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core" import { ProjectTable } from "../project/project.sql" import type { MessageV2 } from "./message-v2" -import type { Snapshot } from "@/snapshot" -import type { PermissionNext } from "@/permission/next" -import { Timestamps } from "@/storage/schema.sql" +import type { Snapshot } from "../snapshot" +import type { PermissionNext } from "../permission/next" +import { Timestamps } from "../storage/schema.sql" type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID"> type InfoData = Omit<MessageV2.Info, "id" | "sessionID"> diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 5443762784..73fd21d905 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -1,4 +1,5 @@ import { Bus } from "@/bus" +import { Account } from "@/account" import { Config } from "@/config/config" import { Provider } from "@/provider/provider" import { Session } from "@/session" @@ -11,8 +12,51 @@ import type * as SDK from "@opencode-ai/sdk/v2" export namespace ShareNext { const log = Log.create({ service: "share-next" }) + type ApiEndpoints = { + create: string + sync: (shareId: string) => string + remove: (shareId: string) => string + data: (shareId: string) => string + } + + function apiEndpoints(resource: string): ApiEndpoints { + return { + create: `/api/${resource}`, + sync: (shareId) => `/api/${resource}/${shareId}/sync`, + remove: (shareId) => `/api/${resource}/${shareId}`, + data: (shareId) => `/api/${resource}/${shareId}/data`, + } + } + + const legacyApi = apiEndpoints("share") + const consoleApi = apiEndpoints("shares") + export async function url() { - return Config.get().then((x) => x.enterprise?.url ?? "https://opncd.ai") + const req = await request() + return req.baseUrl + } + + export async function request(): Promise<{ + headers: Record<string, string> + api: ApiEndpoints + baseUrl: string + }> { + const headers: Record<string, string> = {} + + const active = Account.active() + if (!active?.active_org_id) { + const baseUrl = await Config.get().then((x) => x.enterprise?.url ?? "https://opncd.ai") + return { headers, api: legacyApi, baseUrl } + } + + const token = await Account.token(active.id) + if (!token) { + throw new Error("No active account token available for sharing") + } + + headers["authorization"] = `Bearer ${token}` + headers["x-org-id"] = active.active_org_id + return { headers, api: consoleApi, baseUrl: active.url } } const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1" @@ -68,15 +112,20 @@ export namespace ShareNext { export async function create(sessionID: string) { if (disabled) return { id: "", url: "", secret: "" } log.info("creating share", { sessionID }) - const result = await fetch(`${await url()}/api/share`, { + const req = await request() + const response = await fetch(`${req.baseUrl}${req.api.create}`, { method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers: { ...req.headers, "Content-Type": "application/json" }, body: JSON.stringify({ sessionID: sessionID }), }) - .then((x) => x.json()) - .then((x) => x as { id: string; url: string; secret: string }) + + if (!response.ok) { + const message = await response.text().catch(() => response.statusText) + throw new Error(`Failed to create share (${response.status}): ${message || response.statusText}`) + } + + const result = (await response.json()) as { id: string; url: string; secret: string } + Database.use((db) => db .insert(SessionShareTable) @@ -159,16 +208,19 @@ export namespace ShareNext { const share = get(sessionID) if (!share) return - await fetch(`${await url()}/api/share/${share.id}/sync`, { + const req = await request() + const response = await fetch(`${req.baseUrl}${req.api.sync(share.id)}`, { method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers: { ...req.headers, "Content-Type": "application/json" }, body: JSON.stringify({ secret: share.secret, data: Array.from(queued.data.values()), }), }) + + if (!response.ok) { + log.warn("failed to sync share", { sessionID, shareID: share.id, status: response.status }) + } }, 1000) queue.set(sessionID, { timeout, data: dataMap }) } @@ -178,15 +230,21 @@ export namespace ShareNext { log.info("removing share", { sessionID }) const share = get(sessionID) if (!share) return - await fetch(`${await url()}/api/share/${share.id}`, { + + const req = await request() + const response = await fetch(`${req.baseUrl}${req.api.remove(share.id)}`, { method: "DELETE", - headers: { - "Content-Type": "application/json", - }, + headers: { ...req.headers, "Content-Type": "application/json" }, body: JSON.stringify({ secret: share.secret, }), }) + + if (!response.ok) { + const message = await response.text().catch(() => response.statusText) + throw new Error(`Failed to remove share (${response.status}): ${message || response.statusText}`) + } + Database.use((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run()) } diff --git a/packages/opencode/src/share/share.sql.ts b/packages/opencode/src/share/share.sql.ts index 268d41a6f6..f337e106a5 100644 --- a/packages/opencode/src/share/share.sql.ts +++ b/packages/opencode/src/share/share.sql.ts @@ -1,6 +1,6 @@ import { sqliteTable, text } from "drizzle-orm/sqlite-core" import { SessionTable } from "../session/session.sql" -import { Timestamps } from "@/storage/schema.sql" +import { Timestamps } from "../storage/schema.sql" export const SessionShareTable = sqliteTable("session_share", { session_id: text() diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index cd920a8907..beb8e3eb52 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -39,7 +39,7 @@ export namespace Database { type Schema = typeof schema export type Transaction = SQLiteTransaction<"sync", void, Schema> - type Client = SQLiteBunDatabase<Schema> + type Client = SQLiteBunDatabase type Journal = { sql: string; timestamp: number; name: string }[] @@ -93,7 +93,7 @@ export namespace Database { sqlite.run("PRAGMA foreign_keys = ON") sqlite.run("PRAGMA wal_checkpoint(PASSIVE)") - const db = drizzle({ client: sqlite, schema }) + const db = drizzle({ client: sqlite }) // Apply schema migrations const entries = @@ -124,7 +124,7 @@ export namespace Database { Client.reset() } - export type TxOrDb = Transaction | Client + export type TxOrDb = SQLiteTransaction<"sync", void, any, any> | Client const ctx = Context.create<{ tx: TxOrDb diff --git a/packages/opencode/src/storage/schema.ts b/packages/opencode/src/storage/schema.ts index 4c1c2490e3..0c12cee622 100644 --- a/packages/opencode/src/storage/schema.ts +++ b/packages/opencode/src/storage/schema.ts @@ -1,5 +1,5 @@ -export { ControlAccountTable } from "../control/control.sql" +export { AccountTable, AccountStateTable, ControlAccountTable } from "../account/account.sql" +export { ProjectTable } from "../project/project.sql" export { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql" export { SessionShareTable } from "../share/share.sql" -export { ProjectTable } from "../project/project.sql" export { WorkspaceTable } from "../control-plane/workspace.sql" diff --git a/packages/opencode/src/util/effect-http-client.ts b/packages/opencode/src/util/effect-http-client.ts new file mode 100644 index 0000000000..0c95e34b52 --- /dev/null +++ b/packages/opencode/src/util/effect-http-client.ts @@ -0,0 +1,11 @@ +import { Schedule } from "effect" +import { HttpClient } from "effect/unstable/http" + +export const withTransientReadRetry = <E, R>(client: HttpClient.HttpClient.With<E, R>) => + client.pipe( + HttpClient.retryTransient({ + retryOn: "errors-and-responses", + times: 2, + schedule: Schedule.exponential(200).pipe(Schedule.jittered), + }), + ) diff --git a/packages/opencode/src/util/schema.ts b/packages/opencode/src/util/schema.ts new file mode 100644 index 0000000000..180f952d73 --- /dev/null +++ b/packages/opencode/src/util/schema.ts @@ -0,0 +1,17 @@ +import { Schema } from "effect" + +/** + * Attach static methods to a schema object. Designed to be used with `.pipe()`: + * + * @example + * export const Foo = fooSchema.pipe( + * withStatics((schema) => ({ + * zero: schema.makeUnsafe(0), + * from: Schema.decodeUnknownOption(schema), + * })) + * ) + */ +export const withStatics = + <S extends object, M extends Record<string, unknown>>(methods: (schema: S) => M) => + (schema: S): S & M => + Object.assign(schema, methods(schema)) diff --git a/packages/opencode/test/account/repo.test.ts b/packages/opencode/test/account/repo.test.ts new file mode 100644 index 0000000000..ecc392ead5 --- /dev/null +++ b/packages/opencode/test/account/repo.test.ts @@ -0,0 +1,338 @@ +import { expect } from "bun:test" +import { Effect, Layer, Option } from "effect" + +import { AccountRepo } from "../../src/account/repo" +import { AccountID, OrgID } from "../../src/account/schema" +import { Database } from "../../src/storage/db" +import { testEffect } from "../fixture/effect" + +const truncate = Layer.effectDiscard( + Effect.sync(() => { + const db = Database.Client() + db.run(/*sql*/ `DELETE FROM account_state`) + db.run(/*sql*/ `DELETE FROM account`) + }), +) + +const it = testEffect(Layer.merge(AccountRepo.layer, truncate)) + +it.effect( + "list returns empty when no accounts exist", + Effect.gen(function* () { + const accounts = yield* AccountRepo.use((r) => r.list()) + expect(accounts).toEqual([]) + }), +) + +it.effect( + "active returns none when no accounts exist", + Effect.gen(function* () { + const active = yield* AccountRepo.use((r) => r.active()) + expect(Option.isNone(active)).toBe(true) + }), +) + +it.effect( + "persistAccount inserts and getRow retrieves", + Effect.gen(function* () { + const id = AccountID.make("user-1") + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "test@example.com", + url: "https://control.example.com", + accessToken: "at_123", + refreshToken: "rt_456", + expiry: Date.now() + 3600_000, + orgID: Option.some(OrgID.make("org-1")), + }), + ) + + const row = yield* AccountRepo.use((r) => r.getRow(id)) + expect(Option.isSome(row)).toBe(true) + const value = Option.getOrThrow(row) + expect(value.id).toBe("user-1") + expect(value.email).toBe("test@example.com") + + const active = yield* AccountRepo.use((r) => r.active()) + expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-1")) + }), +) + +it.effect( + "persistAccount sets the active account and org", + Effect.gen(function* () { + const id1 = AccountID.make("user-1") + const id2 = AccountID.make("user-2") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id: id1, + email: "first@example.com", + url: "https://control.example.com", + accessToken: "at_1", + refreshToken: "rt_1", + expiry: Date.now() + 3600_000, + orgID: Option.some(OrgID.make("org-1")), + }), + ) + + yield* AccountRepo.use((r) => + r.persistAccount({ + id: id2, + email: "second@example.com", + url: "https://control.example.com", + accessToken: "at_2", + refreshToken: "rt_2", + expiry: Date.now() + 3600_000, + orgID: Option.some(OrgID.make("org-2")), + }), + ) + + // Last persisted account is active with its org + const active = yield* AccountRepo.use((r) => r.active()) + expect(Option.isSome(active)).toBe(true) + expect(Option.getOrThrow(active).id).toBe(AccountID.make("user-2")) + expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2")) + }), +) + +it.effect( + "list returns all accounts", + Effect.gen(function* () { + const id1 = AccountID.make("user-1") + const id2 = AccountID.make("user-2") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id: id1, + email: "a@example.com", + url: "https://control.example.com", + accessToken: "at_1", + refreshToken: "rt_1", + expiry: Date.now() + 3600_000, + orgID: Option.none(), + }), + ) + + yield* AccountRepo.use((r) => + r.persistAccount({ + id: id2, + email: "b@example.com", + url: "https://control.example.com", + accessToken: "at_2", + refreshToken: "rt_2", + expiry: Date.now() + 3600_000, + orgID: Option.some(OrgID.make("org-1")), + }), + ) + + const accounts = yield* AccountRepo.use((r) => r.list()) + expect(accounts.length).toBe(2) + expect(accounts.map((a) => a.email).sort()).toEqual(["a@example.com", "b@example.com"]) + }), +) + +it.effect( + "remove deletes an account", + Effect.gen(function* () { + const id = AccountID.make("user-1") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "test@example.com", + url: "https://control.example.com", + accessToken: "at_1", + refreshToken: "rt_1", + expiry: Date.now() + 3600_000, + orgID: Option.none(), + }), + ) + + yield* AccountRepo.use((r) => r.remove(id)) + + const row = yield* AccountRepo.use((r) => r.getRow(id)) + expect(Option.isNone(row)).toBe(true) + }), +) + +it.effect( + "use stores the selected org and marks the account active", + Effect.gen(function* () { + const id1 = AccountID.make("user-1") + const id2 = AccountID.make("user-2") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id: id1, + email: "first@example.com", + url: "https://control.example.com", + accessToken: "at_1", + refreshToken: "rt_1", + expiry: Date.now() + 3600_000, + orgID: Option.none(), + }), + ) + + yield* AccountRepo.use((r) => + r.persistAccount({ + id: id2, + email: "second@example.com", + url: "https://control.example.com", + accessToken: "at_2", + refreshToken: "rt_2", + expiry: Date.now() + 3600_000, + orgID: Option.none(), + }), + ) + + yield* AccountRepo.use((r) => r.use(id1, Option.some(OrgID.make("org-99")))) + const active1 = yield* AccountRepo.use((r) => r.active()) + expect(Option.getOrThrow(active1).id).toBe(id1) + expect(Option.getOrThrow(active1).active_org_id).toBe(OrgID.make("org-99")) + + yield* AccountRepo.use((r) => r.use(id1, Option.none())) + const active2 = yield* AccountRepo.use((r) => r.active()) + expect(Option.getOrThrow(active2).active_org_id).toBeNull() + }), +) + +it.effect( + "persistToken updates token fields", + Effect.gen(function* () { + const id = AccountID.make("user-1") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "test@example.com", + url: "https://control.example.com", + accessToken: "old_token", + refreshToken: "old_refresh", + expiry: 1000, + orgID: Option.none(), + }), + ) + + const expiry = Date.now() + 7200_000 + yield* AccountRepo.use((r) => + r.persistToken({ + accountID: id, + accessToken: "new_token", + refreshToken: "new_refresh", + expiry: Option.some(expiry), + }), + ) + + const row = yield* AccountRepo.use((r) => r.getRow(id)) + const value = Option.getOrThrow(row) + expect(value.access_token).toBe("new_token") + expect(value.refresh_token).toBe("new_refresh") + expect(value.token_expiry).toBe(expiry) + }), +) + +it.effect( + "persistToken with no expiry sets token_expiry to null", + Effect.gen(function* () { + const id = AccountID.make("user-1") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "test@example.com", + url: "https://control.example.com", + accessToken: "old_token", + refreshToken: "old_refresh", + expiry: 1000, + orgID: Option.none(), + }), + ) + + yield* AccountRepo.use((r) => + r.persistToken({ + accountID: id, + accessToken: "new_token", + refreshToken: "new_refresh", + expiry: Option.none(), + }), + ) + + const row = yield* AccountRepo.use((r) => r.getRow(id)) + expect(Option.getOrThrow(row).token_expiry).toBeNull() + }), +) + +it.effect( + "persistAccount upserts on conflict", + Effect.gen(function* () { + const id = AccountID.make("user-1") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "test@example.com", + url: "https://control.example.com", + accessToken: "at_v1", + refreshToken: "rt_v1", + expiry: 1000, + orgID: Option.some(OrgID.make("org-1")), + }), + ) + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "test@example.com", + url: "https://control.example.com", + accessToken: "at_v2", + refreshToken: "rt_v2", + expiry: 2000, + orgID: Option.some(OrgID.make("org-2")), + }), + ) + + const accounts = yield* AccountRepo.use((r) => r.list()) + expect(accounts.length).toBe(1) + + const row = yield* AccountRepo.use((r) => r.getRow(id)) + const value = Option.getOrThrow(row) + expect(value.access_token).toBe("at_v2") + + const active = yield* AccountRepo.use((r) => r.active()) + expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2")) + }), +) + +it.effect( + "remove clears active state when deleting the active account", + Effect.gen(function* () { + const id = AccountID.make("user-1") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "test@example.com", + url: "https://control.example.com", + accessToken: "at_1", + refreshToken: "rt_1", + expiry: Date.now() + 3600_000, + orgID: Option.some(OrgID.make("org-1")), + }), + ) + + yield* AccountRepo.use((r) => r.remove(id)) + + const active = yield* AccountRepo.use((r) => r.active()) + expect(Option.isNone(active)).toBe(true) + }), +) + +it.effect( + "getRow returns none for nonexistent account", + Effect.gen(function* () { + const row = yield* AccountRepo.use((r) => r.getRow(AccountID.make("nope"))) + expect(Option.isNone(row)).toBe(true) + }), +) diff --git a/packages/opencode/test/account/service.test.ts b/packages/opencode/test/account/service.test.ts new file mode 100644 index 0000000000..87f5b23f28 --- /dev/null +++ b/packages/opencode/test/account/service.test.ts @@ -0,0 +1,223 @@ +import { expect } from "bun:test" +import { Effect, Layer, Option, Ref, Schema } from "effect" +import { HttpClient, HttpClientResponse } from "effect/unstable/http" + +import { AccountRepo } from "../../src/account/repo" +import { AccountService } from "../../src/account/service" +import { AccountID, Login, Org, OrgID } from "../../src/account/schema" +import { Database } from "../../src/storage/db" +import { testEffect } from "../fixture/effect" + +const truncate = Layer.effectDiscard( + Effect.sync(() => { + const db = Database.Client() + db.run(/*sql*/ `DELETE FROM account_state`) + db.run(/*sql*/ `DELETE FROM account`) + }), +) + +const it = testEffect(Layer.merge(AccountRepo.layer, truncate)) + +const live = (client: HttpClient.HttpClient) => + AccountService.layer.pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, client))) + +const json = (req: Parameters<typeof HttpClientResponse.fromWeb>[0], body: unknown, status = 200) => + HttpClientResponse.fromWeb( + req, + new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, + }), + ) + +const encodeOrg = Schema.encodeSync(Org) + +const org = (id: string, name: string) => encodeOrg(new Org({ id: OrgID.make(id), name })) + +it.effect( + "orgsByAccount groups orgs per account", + Effect.gen(function* () { + yield* AccountRepo.use((r) => + r.persistAccount({ + id: AccountID.make("user-1"), + email: "one@example.com", + url: "https://one.example.com", + accessToken: "at_1", + refreshToken: "rt_1", + expiry: Date.now() + 60_000, + orgID: Option.none(), + }), + ) + + yield* AccountRepo.use((r) => + r.persistAccount({ + id: AccountID.make("user-2"), + email: "two@example.com", + url: "https://two.example.com", + accessToken: "at_2", + refreshToken: "rt_2", + expiry: Date.now() + 60_000, + orgID: Option.none(), + }), + ) + + const seen = yield* Ref.make<string[]>([]) + const client = HttpClient.make((req) => + Effect.gen(function* () { + yield* Ref.update(seen, (xs) => [...xs, `${req.method} ${req.url}`]) + + if (req.url === "https://one.example.com/api/orgs") { + return json(req, [org("org-1", "One")]) + } + + if (req.url === "https://two.example.com/api/orgs") { + return json(req, [org("org-2", "Two A"), org("org-3", "Two B")]) + } + + return json(req, [], 404) + }), + ) + + const rows = yield* AccountService.use((s) => s.orgsByAccount()).pipe(Effect.provide(live(client))) + + expect(rows.map((row) => [row.account.id, row.orgs.map((org) => org.id)]).map(([id, orgs]) => [id, orgs])).toEqual([ + [AccountID.make("user-1"), [OrgID.make("org-1")]], + [AccountID.make("user-2"), [OrgID.make("org-2"), OrgID.make("org-3")]], + ]) + expect(yield* Ref.get(seen)).toEqual([ + "GET https://one.example.com/api/orgs", + "GET https://two.example.com/api/orgs", + ]) + }), +) + +it.effect( + "token refresh persists the new token", + Effect.gen(function* () { + const id = AccountID.make("user-1") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "user@example.com", + url: "https://one.example.com", + accessToken: "at_old", + refreshToken: "rt_old", + expiry: Date.now() - 1_000, + orgID: Option.none(), + }), + ) + + const client = HttpClient.make((req) => + Effect.succeed( + req.url === "https://one.example.com/oauth/token" + ? json(req, { + access_token: "at_new", + refresh_token: "rt_new", + expires_in: 60, + }) + : json(req, {}, 404), + ), + ) + + const token = yield* AccountService.use((s) => s.token(id)).pipe(Effect.provide(live(client))) + + expect(Option.getOrThrow(token)).toBeDefined() + expect(String(Option.getOrThrow(token))).toBe("at_new") + + const row = yield* AccountRepo.use((r) => r.getRow(id)) + const value = Option.getOrThrow(row) + expect(value.access_token).toBe("at_new") + expect(value.refresh_token).toBe("rt_new") + expect(value.token_expiry).toBeGreaterThan(Date.now()) + }), +) + +it.effect( + "config sends the selected org header", + Effect.gen(function* () { + const id = AccountID.make("user-1") + + yield* AccountRepo.use((r) => + r.persistAccount({ + id, + email: "user@example.com", + url: "https://one.example.com", + accessToken: "at_1", + refreshToken: "rt_1", + expiry: Date.now() + 60_000, + orgID: Option.none(), + }), + ) + + const seen = yield* Ref.make<{ auth?: string; org?: string }>({}) + const client = HttpClient.make((req) => + Effect.gen(function* () { + yield* Ref.set(seen, { + auth: req.headers.authorization, + org: req.headers["x-org-id"], + }) + + if (req.url === "https://one.example.com/api/config") { + return json(req, { config: { theme: "light", seats: 5 } }) + } + + return json(req, {}, 404) + }), + ) + + const cfg = yield* AccountService.use((s) => s.config(id, OrgID.make("org-9"))).pipe(Effect.provide(live(client))) + + expect(Option.getOrThrow(cfg)).toEqual({ theme: "light", seats: 5 }) + expect(yield* Ref.get(seen)).toEqual({ + auth: "Bearer at_1", + org: "org-9", + }) + }), +) + +it.effect( + "poll stores the account and first org on success", + Effect.gen(function* () { + const login = new Login({ + code: "device-code", + user: "user-code", + url: "https://one.example.com/verify", + server: "https://one.example.com", + expiry: 600, + interval: 5, + }) + + const client = HttpClient.make((req) => + Effect.succeed( + req.url === "https://one.example.com/auth/device/token" + ? json(req, { + access_token: "at_1", + refresh_token: "rt_1", + expires_in: 60, + }) + : req.url === "https://one.example.com/api/user" + ? json(req, { id: "user-1", email: "user@example.com" }) + : req.url === "https://one.example.com/api/orgs" + ? json(req, [org("org-1", "One")]) + : json(req, {}, 404), + ), + ) + + const res = yield* AccountService.use((s) => s.poll(login)).pipe(Effect.provide(live(client))) + + expect(res._tag).toBe("PollSuccess") + if (res._tag === "PollSuccess") { + expect(res.email).toBe("user@example.com") + } + + const active = yield* AccountRepo.use((r) => r.active()) + expect(Option.getOrThrow(active)).toEqual( + expect.objectContaining({ + id: "user-1", + email: "user@example.com", + active_org_id: "org-1", + }), + ) + }), +) diff --git a/packages/opencode/test/cli/import.test.ts b/packages/opencode/test/cli/import.test.ts index a1a69dc094..922c08114b 100644 --- a/packages/opencode/test/cli/import.test.ts +++ b/packages/opencode/test/cli/import.test.ts @@ -1,5 +1,10 @@ import { test, expect } from "bun:test" -import { parseShareUrl, transformShareData, type ShareData } from "../../src/cli/cmd/import" +import { + parseShareUrl, + shouldAttachShareAuthHeaders, + transformShareData, + type ShareData, +} from "../../src/cli/cmd/import" // parseShareUrl tests test("parses valid share URLs", () => { @@ -15,6 +20,19 @@ test("rejects invalid URLs", () => { expect(parseShareUrl("not-a-url")).toBeNull() }) +test("only attaches share auth headers for same-origin URLs", () => { + expect(shouldAttachShareAuthHeaders("https://control.example.com/share/abc", "https://control.example.com")).toBe( + true, + ) + expect( + shouldAttachShareAuthHeaders("https://other.example.com/share/abc", "https://control.example.com"), + ).toBe(false) + expect(shouldAttachShareAuthHeaders("https://control.example.com:443/share/abc", "https://control.example.com")).toBe( + true, + ) + expect(shouldAttachShareAuthHeaders("not-a-url", "https://control.example.com")).toBe(false) +}) + // transformShareData tests test("transforms share data to storage format", () => { const data: ShareData[] = [ diff --git a/packages/opencode/test/cli/plugin-auth-picker.test.ts b/packages/opencode/test/cli/plugin-auth-picker.test.ts index 3ce9094e92..5a1cf059d5 100644 --- a/packages/opencode/test/cli/plugin-auth-picker.test.ts +++ b/packages/opencode/test/cli/plugin-auth-picker.test.ts @@ -1,5 +1,5 @@ import { test, expect, describe } from "bun:test" -import { resolvePluginProviders } from "../../src/cli/cmd/auth" +import { resolvePluginProviders } from "../../src/cli/cmd/providers" import type { Hooks } from "@opencode-ai/plugin" function hookWithAuth(provider: string): Hooks { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 96fac8cca2..80394fbff5 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -2,6 +2,7 @@ import { test, expect, describe, mock, afterEach } from "bun:test" import { Config } from "../../src/config/config" import { Instance } from "../../src/project/instance" import { Auth } from "../../src/auth" +import { AccessToken, Account, AccountID, OrgID } from "../../src/account" import { tmpdir } from "../fixture/fixture" import path from "path" import fs from "fs/promises" @@ -242,6 +243,52 @@ test("preserves env variables when adding $schema to config", async () => { } }) +test("resolves env templates in account config with account token", async () => { + const originalActive = Account.active + const originalConfig = Account.config + const originalToken = Account.token + const originalControlToken = process.env["OPENCODE_CONSOLE_TOKEN"] + + Account.active = mock(() => ({ + id: AccountID.make("account-1"), + email: "user@example.com", + url: "https://control.example.com", + active_org_id: OrgID.make("org-1"), + })) + + Account.config = mock(async () => ({ + provider: { + opencode: { + options: { + apiKey: "{env:OPENCODE_CONSOLE_TOKEN}", + }, + }, + }, + })) + + Account.token = mock(async () => AccessToken.make("st_test_token")) + + try { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.provider?.["opencode"]?.options?.apiKey).toBe("st_test_token") + }, + }) + } finally { + Account.active = originalActive + Account.config = originalConfig + Account.token = originalToken + if (originalControlToken !== undefined) { + process.env["OPENCODE_CONSOLE_TOKEN"] = originalControlToken + } else { + delete process.env["OPENCODE_CONSOLE_TOKEN"] + } + } +}) + test("handles file inclusion substitution", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/opencode/test/fixture/effect.ts b/packages/opencode/test/fixture/effect.ts new file mode 100644 index 0000000000..b75610139f --- /dev/null +++ b/packages/opencode/test/fixture/effect.ts @@ -0,0 +1,7 @@ +import { test } from "bun:test" +import { Effect, Layer } from "effect" + +export const testEffect = <R, E>(layer: Layer.Layer<R, E, never>) => ({ + effect: <A, E2>(name: string, value: Effect.Effect<A, E2, R>) => + test(name, () => Effect.runPromise(value.pipe(Effect.provide(layer)))), +}) diff --git a/packages/opencode/test/share/share-next.test.ts b/packages/opencode/test/share/share-next.test.ts new file mode 100644 index 0000000000..5be5d02450 --- /dev/null +++ b/packages/opencode/test/share/share-next.test.ts @@ -0,0 +1,76 @@ +import { test, expect, mock } from "bun:test" +import { ShareNext } from "../../src/share/share-next" +import { AccessToken, Account, AccountID, OrgID } from "../../src/account" +import { Config } from "../../src/config/config" + +test("ShareNext.request uses legacy share API without active org account", async () => { + const originalActive = Account.active + const originalConfigGet = Config.get + + Account.active = mock(() => undefined) + Config.get = mock(async () => ({ enterprise: { url: "https://legacy-share.example.com" } })) + + try { + const req = await ShareNext.request() + + expect(req.api.create).toBe("/api/share") + expect(req.api.sync("shr_123")).toBe("/api/share/shr_123/sync") + expect(req.api.remove("shr_123")).toBe("/api/share/shr_123") + expect(req.api.data("shr_123")).toBe("/api/share/shr_123/data") + expect(req.baseUrl).toBe("https://legacy-share.example.com") + expect(req.headers).toEqual({}) + } finally { + Account.active = originalActive + Config.get = originalConfigGet + } +}) + +test("ShareNext.request uses org share API with auth headers when account is active", async () => { + const originalActive = Account.active + const originalToken = Account.token + + Account.active = mock(() => ({ + id: AccountID.make("account-1"), + email: "user@example.com", + url: "https://control.example.com", + active_org_id: OrgID.make("org-1"), + })) + Account.token = mock(async () => AccessToken.make("st_test_token")) + + try { + const req = await ShareNext.request() + + expect(req.api.create).toBe("/api/shares") + expect(req.api.sync("shr_123")).toBe("/api/shares/shr_123/sync") + expect(req.api.remove("shr_123")).toBe("/api/shares/shr_123") + expect(req.api.data("shr_123")).toBe("/api/shares/shr_123/data") + expect(req.baseUrl).toBe("https://control.example.com") + expect(req.headers).toEqual({ + authorization: "Bearer st_test_token", + "x-org-id": "org-1", + }) + } finally { + Account.active = originalActive + Account.token = originalToken + } +}) + +test("ShareNext.request fails when org account has no token", async () => { + const originalActive = Account.active + const originalToken = Account.token + + Account.active = mock(() => ({ + id: AccountID.make("account-1"), + email: "user@example.com", + url: "https://control.example.com", + active_org_id: OrgID.make("org-1"), + })) + Account.token = mock(async () => undefined) + + try { + await expect(ShareNext.request()).rejects.toThrow("No active account token available for sharing") + } finally { + Account.active = originalActive + Account.token = originalToken + } +}) diff --git a/packages/opencode/tsconfig.json b/packages/opencode/tsconfig.json index 9067d84fd6..44335bd80d 100644 --- a/packages/opencode/tsconfig.json +++ b/packages/opencode/tsconfig.json @@ -11,6 +11,11 @@ "paths": { "@/*": ["./src/*"], "@tui/*": ["./src/cli/cmd/tui/*"] - } + }, + "plugins": [{ + "name": "@effect/language-service", + "transform": "@effect/language-service/transform", + "namespaceImportPackages": ["effect", "@effect/*"] + }] } } From 94c128f73b05bb4b14134974d9db8eb7eb671b92 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:56:30 +0000 Subject: [PATCH 056/200] chore: generate --- .../snapshot.json | 117 +++++------------- packages/opencode/src/account/repo.ts | 10 +- packages/opencode/src/account/service.ts | 4 +- packages/opencode/src/cli/cmd/account.ts | 4 +- packages/opencode/test/cli/import.test.ts | 4 +- packages/opencode/tsconfig.json | 12 +- 6 files changed, 50 insertions(+), 101 deletions(-) diff --git a/packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json b/packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json index 37b9eacacc..488ecefffb 100644 --- a/packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json +++ b/packages/opencode/migration/20260309230000_move_org_to_state/snapshot.json @@ -2,10 +2,7 @@ "version": "7", "dialect": "sqlite", "id": "fb311f30-9948-4131-b15c-7d308478a878", - "prevIds": [ - "325559b7-104f-4d2a-a02c-934cfad7cfcc", - "4ec9de62-88a7-4bec-91cc-0a759e84db21" - ], + "prevIds": ["325559b7-104f-4d2a-a02c-934cfad7cfcc", "4ec9de62-88a7-4bec-91cc-0a759e84db21"], "ddl": [ { "name": "account_state", @@ -892,13 +889,9 @@ "table": "session_share" }, { - "columns": [ - "active_account_id" - ], + "columns": ["active_account_id"], "tableTo": "account", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "SET NULL", "nameExplicit": false, @@ -907,13 +900,9 @@ "table": "account_state" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -922,13 +911,9 @@ "table": "workspace" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -937,13 +922,9 @@ "table": "message" }, { - "columns": [ - "message_id" - ], + "columns": ["message_id"], "tableTo": "message", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -952,13 +933,9 @@ "table": "part" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -967,13 +944,9 @@ "table": "permission" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -982,13 +955,9 @@ "table": "session" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -997,13 +966,9 @@ "table": "todo" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1012,101 +977,77 @@ "table": "session_share" }, { - "columns": [ - "email", - "url" - ], + "columns": ["email", "url"], "nameExplicit": false, "name": "control_account_pk", "entityType": "pks", "table": "control_account" }, { - "columns": [ - "session_id", - "position" - ], + "columns": ["session_id", "position"], "nameExplicit": false, "name": "todo_pk", "entityType": "pks", "table": "todo" }, { - "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": "workspace_pk", "table": "workspace", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "project_pk", "table": "project", "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": [ - "project_id" - ], + "columns": ["project_id"], "nameExplicit": false, "name": "permission_pk", "table": "permission", "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", @@ -1212,4 +1153,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/opencode/src/account/repo.ts b/packages/opencode/src/account/repo.ts index ba71abe342..65f56727b9 100644 --- a/packages/opencode/src/account/repo.ts +++ b/packages/opencode/src/account/repo.ts @@ -69,7 +69,15 @@ export class AccountRepo extends ServiceMap.Service< db((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decodeAccount(row)) : Option.none()))), ), - list: Effect.fn("AccountRepo.list")(() => db((db) => db.select().from(AccountTable).all().map((row) => decodeAccount({ ...row, active_org_id: null })))), + list: Effect.fn("AccountRepo.list")(() => + db((db) => + db + .select() + .from(AccountTable) + .all() + .map((row) => decodeAccount({ ...row, active_org_id: null })), + ), + ), remove: Effect.fn("AccountRepo.remove")((accountID: AccountID) => db((db) => diff --git a/packages/opencode/src/account/service.ts b/packages/opencode/src/account/service.ts index 68ea9dbdec..c57bb459a0 100644 --- a/packages/opencode/src/account/service.ts +++ b/packages/opencode/src/account/service.ts @@ -346,7 +346,9 @@ export class AccountService extends ServiceMap.Service< const expiry = now + (parsed.expires_in ?? 0) * 1000 const refresh = parsed.refresh_token ?? "" if (!refresh) { - yield* Effect.logWarning("Server did not return a refresh token — session may expire without ability to refresh") + yield* Effect.logWarning( + "Server did not return a refresh token — session may expire without ability to refresh", + ) } yield* repo.persistAccount({ diff --git a/packages/opencode/src/cli/cmd/account.ts b/packages/opencode/src/cli/cmd/account.ts index 51cf2b138c..7e9f893a8f 100644 --- a/packages/opencode/src/cli/cmd/account.ts +++ b/packages/opencode/src/cli/cmd/account.ts @@ -75,9 +75,7 @@ const logoutEffect = Effect.fn("logout")(function* (email?: string) { const server = UI.Style.TEXT_DIM + a.url + UI.Style.TEXT_NORMAL return { value: a, - label: isActive - ? `${a.email} ${server}` + UI.Style.TEXT_DIM + " (active)" - : `${a.email} ${server}`, + label: isActive ? `${a.email} ${server}` + UI.Style.TEXT_DIM + " (active)" : `${a.email} ${server}`, } }) diff --git a/packages/opencode/test/cli/import.test.ts b/packages/opencode/test/cli/import.test.ts index 922c08114b..d7c0241e6b 100644 --- a/packages/opencode/test/cli/import.test.ts +++ b/packages/opencode/test/cli/import.test.ts @@ -24,9 +24,7 @@ test("only attaches share auth headers for same-origin URLs", () => { expect(shouldAttachShareAuthHeaders("https://control.example.com/share/abc", "https://control.example.com")).toBe( true, ) - expect( - shouldAttachShareAuthHeaders("https://other.example.com/share/abc", "https://control.example.com"), - ).toBe(false) + expect(shouldAttachShareAuthHeaders("https://other.example.com/share/abc", "https://control.example.com")).toBe(false) expect(shouldAttachShareAuthHeaders("https://control.example.com:443/share/abc", "https://control.example.com")).toBe( true, ) diff --git a/packages/opencode/tsconfig.json b/packages/opencode/tsconfig.json index 44335bd80d..ff9886313a 100644 --- a/packages/opencode/tsconfig.json +++ b/packages/opencode/tsconfig.json @@ -12,10 +12,12 @@ "@/*": ["./src/*"], "@tui/*": ["./src/cli/cmd/tui/*"] }, - "plugins": [{ - "name": "@effect/language-service", - "transform": "@effect/language-service/transform", - "namespaceImportPackages": ["effect", "@effect/*"] - }] + "plugins": [ + { + "name": "@effect/language-service", + "transform": "@effect/language-service/transform", + "namespaceImportPackages": ["effect", "@effect/*"] + } + ] } } From bb232247d094b22d497a036c21d4c4a722700a38 Mon Sep 17 00:00:00 2001 From: Dax <mail@thdxr.com> Date: Tue, 10 Mar 2026 13:11:36 -0400 Subject: [PATCH 057/200] Fix ESM imports for @opencode-ai/plugin (#16916) --- packages/plugin/src/example.ts | 4 ++-- packages/plugin/src/index.ts | 6 +++--- packages/plugin/tsconfig.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/plugin/src/example.ts b/packages/plugin/src/example.ts index 94745a37b7..1cf042fe96 100644 --- a/packages/plugin/src/example.ts +++ b/packages/plugin/src/example.ts @@ -1,5 +1,5 @@ -import { Plugin } from "./index" -import { tool } from "./tool" +import { Plugin } from "./index.js" +import { tool } from "./tool.js" export const ExamplePlugin: Plugin = async (ctx) => { return { diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 76370d1d5a..b78bcae177 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -12,10 +12,10 @@ import type { Config, } from "@opencode-ai/sdk" -import type { BunShell } from "./shell" -import { type ToolDefinition } from "./tool" +import type { BunShell } from "./shell.js" +import { type ToolDefinition } from "./tool.js" -export * from "./tool" +export * from "./tool.js" export type ProviderContext = { source: "env" | "config" | "custom" | "api" diff --git a/packages/plugin/tsconfig.json b/packages/plugin/tsconfig.json index 58072c81c9..1173818783 100644 --- a/packages/plugin/tsconfig.json +++ b/packages/plugin/tsconfig.json @@ -3,9 +3,9 @@ "extends": "@tsconfig/node22/tsconfig.json", "compilerOptions": { "outDir": "dist", - "module": "preserve", + "module": "nodenext", "declaration": true, - "moduleResolution": "bundler", + "moduleResolution": "nodenext", "lib": ["es2022", "dom", "dom.iterable"] }, "include": ["src"] From 490615169e6717daab641fed827c21b11ef84a85 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:14:55 +0000 Subject: [PATCH 058/200] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index d4146be6b7..153f78ac6d 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-duBedS4ZTc1as03OM0KB9mKKU21Cywv4o9GHwQZv6Ts=", - "aarch64-linux": "sha256-juvQfuNBqqzeB/TIY9PuUDqgpsdyI54ImowjQLrNhns=", - "aarch64-darwin": "sha256-kKgcuEN1oJqHJc+sGjcZ4INWvbZczSTDJ8VHIWAquD4=", - "x86_64-darwin": "sha256-hXkFWOL4wi9s8HSrChpqtH4PKSNzbzVgU+0GbAxEUT4=" + "x86_64-linux": "sha256-dhL4YeSi4Lm9yDp919Fx7N2hyLUbZQa2qWoCf/50ce8=", + "aarch64-linux": "sha256-//YxCsrvYlxuvd0MtFFO+pLxjmuemyrvGzSIPxzO+rA=", + "aarch64-darwin": "sha256-c65kSWteQNaBcQUsjbXNqT61vt98JPNYo9yMNvUygCw=", + "x86_64-darwin": "sha256-hlTzEFv3nZHwlDXU65LfMC+NaqYjjyZqagdJ366CNxY=" } } From 85afaaa13d693f400d8ec8e257fec086a58b68c1 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 10 Mar 2026 07:33:54 -0500 Subject: [PATCH 059/200] fix(app): terminal focus issues and jank --- .../app/src/pages/session/terminal-panel.tsx | 70 +++++++++++++------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx index 8fd652e903..19a656b530 100644 --- a/packages/app/src/pages/session/terminal-panel.tsx +++ b/packages/app/src/pages/session/terminal-panel.tsx @@ -1,4 +1,4 @@ -import { For, Show, createEffect, createMemo, on } from "solid-js" +import { For, Show, createEffect, createMemo, on, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { createMediaQuery } from "@solid-primitives/media" import { useParams } from "@solidjs/router" @@ -17,7 +17,7 @@ import { useLanguage } from "@/context/language" import { useLayout } from "@/context/layout" import { useTerminal, type LocalPTY } from "@/context/terminal" import { terminalTabLabel } from "@/pages/session/terminal-label" -import { createPresence, createSizing, focusTerminalById } from "@/pages/session/helpers" +import { createSizing, focusTerminalById } from "@/pages/session/helpers" import { getTerminalHandoff, setTerminalHandoff } from "@/pages/session/handoff" export function TerminalPanel() { @@ -33,7 +33,6 @@ export function TerminalPanel() { const opened = createMemo(() => view().terminal.opened()) const open = createMemo(() => isDesktop() && opened()) - const panel = createPresence(open) const size = createSizing() const height = createMemo(() => layout.terminal.height()) const close = () => view().terminal.close() @@ -66,21 +65,42 @@ export function TerminalPanel() { ), ) + const focus = (id: string) => { + focusTerminalById(id) + + const frame = requestAnimationFrame(() => { + if (!open()) return + if (terminal.active() !== id) return + focusTerminalById(id) + }) + + const timers = [120, 240].map((ms) => + window.setTimeout(() => { + if (!open()) return + if (terminal.active() !== id) return + focusTerminalById(id) + }, ms), + ) + + return () => { + cancelAnimationFrame(frame) + for (const timer of timers) clearTimeout(timer) + } + } + createEffect( on( - () => terminal.active(), - (activeId) => { - if (!activeId || !panel.open()) return - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur() - } - setTimeout(() => focusTerminalById(activeId), 0) + () => [open(), terminal.active()] as const, + ([next, id]) => { + if (!next || !id) return + const stop = focus(id) + onCleanup(stop) }, ), ) createEffect(() => { - if (panel.open()) return + if (open()) return const active = document.activeElement if (!(active instanceof HTMLElement)) return if (!root?.contains(active)) return @@ -138,30 +158,38 @@ export function TerminalPanel() { const activeId = terminal.active() if (!activeId) return - setTimeout(() => { + requestAnimationFrame(() => { + if (terminal.active() !== activeId) return focusTerminalById(activeId) - }, 0) + }) } return ( - <Show when={panel.show()}> + <Show when={isDesktop()}> <div ref={root} id="terminal-panel" role="region" aria-label={language.t("terminal.title")} - aria-hidden={!panel.open()} - inert={!panel.open()} + aria-hidden={!open()} + inert={!open()} class="relative w-full shrink-0 overflow-hidden" classList={{ - "opacity-100": panel.open(), - "opacity-0 pointer-events-none": !panel.open(), - "transition-[height,opacity] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none": + "transition-[height] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none": !size.active(), }} - style={{ height: panel.open() ? `${height()}px` : "0px" }} + style={{ height: open() ? `${height()}px` : "0px" }} > - <div class="size-full flex flex-col border-t border-border-weak-base"> + <div + class="absolute inset-x-0 top-0 flex flex-col border-t border-border-weak-base" + classList={{ + "translate-y-0 opacity-100": open(), + "translate-y-full opacity-0 pointer-events-none": !open(), + "transition-[transform,opacity] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[transform,opacity] motion-reduce:transition-none": + !size.active(), + }} + style={{ height: `${height()}px` }} + > <div onPointerDown={() => size.start()}> <ResizeHandle direction="vertical" From 835a27cf517fae5d9952c30989de8be8f760d7a5 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:29:57 -0500 Subject: [PATCH 060/200] fix(app): terminal jank --- .../src/components/session/session-header.tsx | 75 +++-- packages/app/src/components/terminal.tsx | 5 +- packages/app/src/pages/session.tsx | 11 +- .../app/src/pages/session/terminal-panel.tsx | 292 +++++++++--------- 4 files changed, 208 insertions(+), 175 deletions(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 9b4551584c..97f0530e98 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -21,6 +21,8 @@ import { useLayout } from "@/context/layout" import { usePlatform } from "@/context/platform" import { useServer } from "@/context/server" import { useSync } from "@/context/sync" +import { useTerminal } from "@/context/terminal" +import { focusTerminalById } from "@/pages/session/helpers" import { decode64 } from "@/utils/base64" import { Persist, persisted } from "@/utils/persist" import { StatusPopover } from "../status-popover" @@ -229,6 +231,7 @@ export function SessionHeader() { const sync = useSync() const platform = usePlatform() const language = useLanguage() + const terminal = useTerminal() const projectDirectory = createMemo(() => decode64(params.dir) ?? "") const project = createMemo(() => { @@ -296,6 +299,16 @@ export function SessionHeader() { ] as const }) + const toggleTerminal = () => { + const next = !view().terminal.opened() + view().terminal.toggle() + if (!next) return + + const id = terminal.active() + if (!id) return + focusTerminalById(id) + } + const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp })) const [menu, setMenu] = createStore({ open: false }) const [openRequest, setOpenRequest] = createStore({ @@ -617,39 +630,39 @@ export function SessionHeader() { </div> </Show> <div class="flex items-center gap-1"> - <div class="hidden md:flex items-center gap-1 shrink-0"> - <TooltipKeybind - title={language.t("command.terminal.toggle")} - keybind={command.keybind("terminal.toggle")} + <TooltipKeybind + title={language.t("command.terminal.toggle")} + keybind={command.keybind("terminal.toggle")} + > + <Button + variant="ghost" + class="group/terminal-toggle titlebar-icon w-8 h-6 p-0 box-border shrink-0" + onClick={toggleTerminal} + aria-label={language.t("command.terminal.toggle")} + aria-expanded={view().terminal.opened()} + aria-controls="terminal-panel" > - <Button - variant="ghost" - class="group/terminal-toggle titlebar-icon w-8 h-6 p-0 box-border" - onClick={() => view().terminal.toggle()} - aria-label={language.t("command.terminal.toggle")} - aria-expanded={view().terminal.opened()} - aria-controls="terminal-panel" - > - <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0"> - <Icon - size="small" - name={view().terminal.opened() ? "layout-bottom-partial" : "layout-bottom"} - class="group-hover/terminal-toggle:hidden" - /> - <Icon - size="small" - name="layout-bottom-partial" - class="hidden group-hover/terminal-toggle:inline-block" - /> - <Icon - size="small" - name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-partial"} - class="hidden group-active/terminal-toggle:inline-block" - /> - </div> - </Button> - </TooltipKeybind> + <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0"> + <Icon + size="small" + name={view().terminal.opened() ? "layout-bottom-partial" : "layout-bottom"} + class="group-hover/terminal-toggle:hidden" + /> + <Icon + size="small" + name="layout-bottom-partial" + class="hidden group-hover/terminal-toggle:inline-block" + /> + <Icon + size="small" + name={view().terminal.opened() ? "layout-bottom" : "layout-bottom-partial"} + class="hidden group-active/terminal-toggle:inline-block" + /> + </div> + </Button> + </TooltipKeybind> + <div class="hidden md:flex items-center gap-1 shrink-0"> <TooltipKeybind title={language.t("command.review.toggle")} keybind={command.keybind("review.toggle")} diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index 9e5f12ee4c..120af0a172 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -17,6 +17,7 @@ const TOGGLE_TERMINAL_ID = "terminal.toggle" const DEFAULT_TOGGLE_TERMINAL_KEYBIND = "ctrl+`" export interface TerminalProps extends ComponentProps<"div"> { pty: LocalPTY + autoFocus?: boolean onSubmit?: () => void onCleanup?: (pty: Partial<LocalPTY> & { id: string }) => void onConnect?: () => void @@ -157,7 +158,7 @@ export const Terminal = (props: TerminalProps) => { const language = useLanguage() const server = useServer() let container!: HTMLDivElement - const [local, others] = splitProps(props, ["pty", "class", "classList", "onConnect", "onConnectError"]) + const [local, others] = splitProps(props, ["pty", "class", "classList", "autoFocus", "onConnect", "onConnectError"]) const id = local.pty.id const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : "" const restoreSize = @@ -386,7 +387,7 @@ export const Terminal = (props: TerminalProps) => { handleLinkClick, }) - focusTerminal() + if (local.autoFocus !== false) focusTerminal() if (typeof document !== "undefined" && document.fonts) { document.fonts.ready.then(scheduleFit) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index c1552ad024..79c8d42f55 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -32,8 +32,9 @@ import { useLayout } from "@/context/layout" import { usePrompt } from "@/context/prompt" import { useSDK } from "@/context/sdk" import { useSync } from "@/context/sync" +import { useTerminal } from "@/context/terminal" import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer" -import { createOpenReviewFile, createSizing } from "@/pages/session/helpers" +import { createOpenReviewFile, createSizing, focusTerminalById } from "@/pages/session/helpers" import { MessageTimeline } from "@/pages/session/message-timeline" import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab" import { resetSessionModel, syncSessionModel } from "@/pages/session/session-model-helpers" @@ -267,6 +268,7 @@ export default function Page() { const sdk = useSDK() const prompt = usePrompt() const comments = useComments() + const terminal = useTerminal() const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>() createEffect(() => { @@ -759,8 +761,11 @@ export default function Page() { return } - // Don't autofocus chat if desktop terminal panel is open - if (isDesktop() && view().terminal.opened()) return + // Prefer the open terminal over the composer when it can take focus + if (view().terminal.opened()) { + const id = terminal.active() + if (id && focusTerminalById(id)) return + } // Only treat explicit scroll keys as potential "user scroll" gestures. if (event.key === "PageUp" || event.key === "PageDown" || event.key === "Home" || event.key === "End") { diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx index 19a656b530..a6c3929c10 100644 --- a/packages/app/src/pages/session/terminal-panel.tsx +++ b/packages/app/src/pages/session/terminal-panel.tsx @@ -1,6 +1,5 @@ import { For, Show, createEffect, createMemo, on, onCleanup } from "solid-js" import { createStore } from "solid-js/store" -import { createMediaQuery } from "@solid-primitives/media" import { useParams } from "@solidjs/router" import { Tabs } from "@opencode-ai/ui/tabs" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" @@ -27,12 +26,10 @@ export function TerminalPanel() { const language = useLanguage() const command = useCommand() - const isDesktop = createMediaQuery("(min-width: 768px)") const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) const view = createMemo(() => layout.view(sessionKey)) const opened = createMemo(() => view().terminal.opened()) - const open = createMemo(() => isDesktop() && opened()) const size = createSizing() const height = createMemo(() => layout.terminal.height()) const close = () => view().terminal.close() @@ -41,6 +38,25 @@ export function TerminalPanel() { const [store, setStore] = createStore({ autoCreated: false, activeDraggable: undefined as string | undefined, + view: typeof window === "undefined" ? 1000 : (window.visualViewport?.height ?? window.innerHeight), + }) + + const max = () => store.view * 0.6 + const pane = () => Math.min(height(), max()) + + createEffect(() => { + if (typeof window === "undefined") return + + const sync = () => setStore("view", window.visualViewport?.height ?? window.innerHeight) + const port = window.visualViewport + + sync() + window.addEventListener("resize", sync) + port?.addEventListener("resize", sync) + onCleanup(() => { + window.removeEventListener("resize", sync) + port?.removeEventListener("resize", sync) + }) }) createEffect(() => { @@ -69,14 +85,14 @@ export function TerminalPanel() { focusTerminalById(id) const frame = requestAnimationFrame(() => { - if (!open()) return + if (!opened()) return if (terminal.active() !== id) return focusTerminalById(id) }) const timers = [120, 240].map((ms) => window.setTimeout(() => { - if (!open()) return + if (!opened()) return if (terminal.active() !== id) return focusTerminalById(id) }, ms), @@ -90,7 +106,7 @@ export function TerminalPanel() { createEffect( on( - () => [open(), terminal.active()] as const, + () => [opened(), terminal.active()] as const, ([next, id]) => { if (!next || !id) return const stop = focus(id) @@ -100,7 +116,7 @@ export function TerminalPanel() { ) createEffect(() => { - if (open()) return + if (opened()) return const active = document.activeElement if (!(active instanceof HTMLElement)) return if (!root?.contains(active)) return @@ -165,151 +181,149 @@ export function TerminalPanel() { } return ( - <Show when={isDesktop()}> + <div + ref={root} + id="terminal-panel" + role="region" + aria-label={language.t("terminal.title")} + aria-hidden={!opened()} + inert={!opened()} + class="relative w-full shrink-0 overflow-hidden bg-background-stronger" + classList={{ + "border-t border-border-weak-base": opened(), + "transition-[height] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none": + !size.active(), + }} + style={{ height: opened() ? `${pane()}px` : "0px" }} + > <div - ref={root} - id="terminal-panel" - role="region" - aria-label={language.t("terminal.title")} - aria-hidden={!open()} - inert={!open()} - class="relative w-full shrink-0 overflow-hidden" + class="absolute inset-x-0 top-0 flex flex-col" classList={{ - "transition-[height] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none": + "translate-y-0": opened(), + "translate-y-full pointer-events-none": !opened(), + "transition-transform duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none": !size.active(), }} - style={{ height: open() ? `${height()}px` : "0px" }} + style={{ height: `${pane()}px` }} > - <div - class="absolute inset-x-0 top-0 flex flex-col border-t border-border-weak-base" - classList={{ - "translate-y-0 opacity-100": open(), - "translate-y-full opacity-0 pointer-events-none": !open(), - "transition-[transform,opacity] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[transform,opacity] motion-reduce:transition-none": - !size.active(), - }} - style={{ height: `${height()}px` }} - > - <div onPointerDown={() => size.start()}> - <ResizeHandle - direction="vertical" - size={height()} - min={100} - max={typeof window === "undefined" ? 1000 : window.innerHeight * 0.6} - collapseThreshold={50} - onResize={(next) => { - size.touch() - layout.terminal.resize(next) - }} - onCollapse={close} - /> - </div> - <Show - when={terminal.ready()} - fallback={ - <div class="flex flex-col h-full pointer-events-none"> - <div class="h-10 flex items-center gap-2 px-2 border-b border-border-weaker-base bg-background-stronger overflow-hidden"> - <For each={handoff()}> - {(title) => ( - <div class="px-2 py-1 rounded-md bg-surface-base text-14-regular text-text-weak truncate max-w-40"> - {title} - </div> - )} - </For> - <div class="flex-1" /> - <div class="text-text-weak pr-2"> - {language.t("common.loading")} - {language.t("common.loading.ellipsis")} - </div> - </div> - <div class="flex-1 flex items-center justify-center text-text-weak"> - {language.t("terminal.loading")} - </div> - </div> - } - > - <DragDropProvider - onDragStart={handleTerminalDragStart} - onDragEnd={handleTerminalDragEnd} - onDragOver={handleTerminalDragOver} - collisionDetector={closestCenter} - > - <DragDropSensors /> - <ConstrainDragYAxis /> - <div class="flex flex-col h-full"> - <Tabs - variant="alt" - value={terminal.active()} - onChange={(id) => terminal.open(id)} - class="!h-auto !flex-none" - > - <Tabs.List class="h-10 border-b border-border-weaker-base"> - <SortableProvider ids={ids()}> - <For each={ids()}> - {(id) => ( - <Show when={byId().get(id)}> - {(pty) => <SortableTerminalTab terminal={pty()} onClose={close} />} - </Show> - )} - </For> - </SortableProvider> - <div class="h-full flex items-center justify-center"> - <TooltipKeybind - title={language.t("command.terminal.new")} - keybind={command.keybind("terminal.new")} - class="flex items-center" - > - <IconButton - icon="plus-small" - variant="ghost" - iconSize="large" - onClick={terminal.new} - aria-label={language.t("command.terminal.new")} - /> - </TooltipKeybind> + <div class="hidden md:block" onPointerDown={() => size.start()}> + <ResizeHandle + direction="vertical" + size={pane()} + min={100} + max={max()} + collapseThreshold={50} + onResize={(next) => { + size.touch() + layout.terminal.resize(next) + }} + onCollapse={close} + /> + </div> + <Show + when={terminal.ready()} + fallback={ + <div class="flex flex-col h-full pointer-events-none"> + <div class="h-10 flex items-center gap-2 px-2 border-b border-border-weaker-base bg-background-stronger overflow-hidden"> + <For each={handoff()}> + {(title) => ( + <div class="px-2 py-1 rounded-md bg-surface-base text-14-regular text-text-weak truncate max-w-40"> + {title} </div> - </Tabs.List> - </Tabs> - <div class="flex-1 min-h-0 relative"> - <Show when={terminal.active()} keyed> - {(id) => ( - <Show when={byId().get(id)}> - {(pty) => ( - <div id={`terminal-wrapper-${id}`} class="absolute inset-0"> - <Terminal - pty={pty()} - onConnect={() => terminal.trim(id)} - onCleanup={terminal.update} - onConnectError={() => terminal.clone(id)} - /> - </div> - )} - </Show> - )} - </Show> + )} + </For> + <div class="flex-1" /> + <div class="text-text-weak pr-2"> + {language.t("common.loading")} + {language.t("common.loading.ellipsis")} </div> </div> - <DragOverlay> - <Show when={store.activeDraggable}> - {(draggedId) => ( - <Show when={byId().get(draggedId())}> - {(t) => ( - <div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular"> - {terminalTabLabel({ - title: t().title, - titleNumber: t().titleNumber, - t: language.t as (key: string, vars?: Record<string, string | number | boolean>) => string, - })} + <div class="flex-1 flex items-center justify-center text-text-weak">{language.t("terminal.loading")}</div> + </div> + } + > + <DragDropProvider + onDragStart={handleTerminalDragStart} + onDragEnd={handleTerminalDragEnd} + onDragOver={handleTerminalDragOver} + collisionDetector={closestCenter} + > + <DragDropSensors /> + <ConstrainDragYAxis /> + <div class="flex flex-col h-full"> + <Tabs + variant="alt" + value={terminal.active()} + onChange={(id) => terminal.open(id)} + class="!h-auto !flex-none" + > + <Tabs.List class="h-10 border-b border-border-weaker-base"> + <SortableProvider ids={ids()}> + <For each={ids()}> + {(id) => ( + <Show when={byId().get(id)}> + {(pty) => <SortableTerminalTab terminal={pty()} onClose={close} />} + </Show> + )} + </For> + </SortableProvider> + <div class="h-full flex items-center justify-center"> + <TooltipKeybind + title={language.t("command.terminal.new")} + keybind={command.keybind("terminal.new")} + class="flex items-center" + > + <IconButton + icon="plus-small" + variant="ghost" + iconSize="large" + onClick={terminal.new} + aria-label={language.t("command.terminal.new")} + /> + </TooltipKeybind> + </div> + </Tabs.List> + </Tabs> + <div class="flex-1 min-h-0 relative"> + <Show when={terminal.active()} keyed> + {(id) => ( + <Show when={byId().get(id)}> + {(pty) => ( + <div id={`terminal-wrapper-${id}`} class="absolute inset-0"> + <Terminal + pty={pty()} + autoFocus={opened()} + onConnect={() => terminal.trim(id)} + onCleanup={terminal.update} + onConnectError={() => terminal.clone(id)} + /> </div> )} </Show> )} </Show> - </DragOverlay> - </DragDropProvider> - </Show> - </div> + </div> + </div> + <DragOverlay> + <Show when={store.activeDraggable}> + {(draggedId) => ( + <Show when={byId().get(draggedId())}> + {(t) => ( + <div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular"> + {terminalTabLabel({ + title: t().title, + titleNumber: t().titleNumber, + t: language.t as (key: string, vars?: Record<string, string | number | boolean>) => string, + })} + </div> + )} + </Show> + )} + </Show> + </DragOverlay> + </DragDropProvider> + </Show> </div> - </Show> + </div> ) } From f5d7fe3072a38d0378b59c9f163f980033dbd430 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:00:10 -0500 Subject: [PATCH 061/200] chore: cleanup --- packages/app/src/pages/session/composer/session-todo-dock.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/app/src/pages/session/composer/session-todo-dock.tsx b/packages/app/src/pages/session/composer/session-todo-dock.tsx index da2b8c8da1..04aeb63172 100644 --- a/packages/app/src/pages/session/composer/session-todo-dock.tsx +++ b/packages/app/src/pages/session/composer/session-todo-dock.tsx @@ -138,7 +138,6 @@ export function SessionTodoDock(props: { "--tool-motion-mask-height": `${props.countMaskHeight ?? 0}px`, "--tool-motion-spring-ms": `${props.countWidthDuration ?? 560}ms`, opacity: `${Math.max(0, Math.min(1, 1 - shut()))}`, - filter: `blur(${Math.max(0, Math.min(1, shut())) * 2}px)`, }} > <AnimatedNumber value={done()} /> @@ -196,7 +195,6 @@ export function SessionTodoDock(props: { style={{ visibility: off() ? "hidden" : "visible", opacity: `${Math.max(0, Math.min(1, 1 - hide()))}`, - filter: `blur(${Math.max(0, Math.min(1, hide())) * 2}px)`, }} > <TodoList todos={props.todos} open={!store.collapsed} /> From e44320980d6ede1313fce74d6ff8d8b439f61fb1 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Tue, 10 Mar 2026 14:08:38 -0400 Subject: [PATCH 062/200] ci: install setuptools to prevent Python distutils errors during dependency installation --- .github/actions/setup-bun/action.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 6c632f7e07..3f06da5197 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -31,6 +31,10 @@ runs: bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }} bun-download-url: ${{ steps.bun-url.outputs.url }} + - name: Install setuptools for distutils compatibility + run: python3 -m pip install setuptools || pip install setuptools || true + shell: bash + - name: Install dependencies run: bun install shell: bash From 1fe8d4d7ad32fecd40609140c0c7ff53016d8ddd Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Tue, 10 Mar 2026 14:08:55 -0400 Subject: [PATCH 063/200] ci: exclude draft PRs from beta labeling process to prevent unfinished work from being included in releases --- script/beta.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/beta.ts b/script/beta.ts index b0e6c2dcc1..61f9cf8620 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -79,7 +79,8 @@ async function fix(pr: PR, files: string[]) { async function main() { console.log("Fetching open PRs with beta label...") - const stdout = await $`gh pr list --state open --label beta --json number,title,author,labels --limit 100`.text() + const stdout = + await $`gh pr list --state open --draft=false --label beta --json number,title,author,labels --limit 100`.text() const prs: PR[] = JSON.parse(stdout).sort((a: PR, b: PR) => a.number - b.number) console.log(`Found ${prs.length} open PRs with beta label`) From e6cdc21f2ddd7264a75eb389693d4d62367ae38c Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Tue, 10 Mar 2026 14:40:14 -0400 Subject: [PATCH 064/200] zen: raise limit --- .../console/app/src/routes/zen/util/handler.ts | 8 +++++++- .../console/app/src/routes/zen/util/rateLimiter.ts | 14 +++++++++++--- packages/console/core/src/model.ts | 1 + packages/console/core/src/subscription.ts | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 429ce00185..d0d8f172bf 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -99,7 +99,13 @@ export async function handler( const dataDumper = createDataDumper(sessionId, requestId, projectId) const trialLimiter = createTrialLimiter(modelInfo.trialProvider, ip) const trialProvider = await trialLimiter?.check() - const rateLimiter = createRateLimiter(modelInfo.allowAnonymous, ip, input.request) + const rateLimiter = createRateLimiter( + modelInfo.id, + modelInfo.allowAnonymous, + modelInfo.rateLimit, + ip, + input.request, + ) await rateLimiter?.check() const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId) const stickyProvider = await stickyTracker?.get() diff --git a/packages/console/app/src/routes/zen/util/rateLimiter.ts b/packages/console/app/src/routes/zen/util/rateLimiter.ts index 019e68754d..152ed9b8d1 100644 --- a/packages/console/app/src/routes/zen/util/rateLimiter.ts +++ b/packages/console/app/src/routes/zen/util/rateLimiter.ts @@ -6,17 +6,25 @@ import { i18n } from "~/i18n" import { localeFromRequest } from "~/lib/language" import { Subscription } from "@opencode-ai/console-core/subscription.js" -export function createRateLimiter(allowAnonymous: boolean | undefined, rawIp: string, request: Request) { +export function createRateLimiter( + modelId: string, + allowAnonymous: boolean | undefined, + rateLimit: number | undefined, + rawIp: string, + request: Request, +) { if (!allowAnonymous) return const dict = i18n(localeFromRequest(request)) const limits = Subscription.getFreeLimits() const limitValue = - limits.checkHeader && !request.headers.get(limits.checkHeader) ? limits.fallbackValue : limits.dailyRequests + limits.checkHeader && !request.headers.get(limits.checkHeader) + ? limits.fallbackValue + : (rateLimit ?? limits.dailyRequests) const ip = !rawIp.length ? "unknown" : rawIp const now = Date.now() - const interval = buildYYYYMMDD(now) + const interval = rateLimit ? `${buildYYYYMMDD(now)}${modelId.substring(0, 2)}` : buildYYYYMMDD(now) return { track: async () => { diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index e4fa022494..223839bf13 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -28,6 +28,7 @@ export namespace ZenData { stickyProvider: z.enum(["strict", "prefer"]).optional(), trialProvider: z.string().optional(), fallbackProvider: z.string().optional(), + rateLimit: z.number().optional(), providers: z.array( z.object({ id: z.string(), diff --git a/packages/console/core/src/subscription.ts b/packages/console/core/src/subscription.ts index 9d6c3ce2b5..994feaff0d 100644 --- a/packages/console/core/src/subscription.ts +++ b/packages/console/core/src/subscription.ts @@ -8,6 +8,7 @@ export namespace Subscription { const LimitsSchema = z.object({ free: z.object({ promoTokens: z.number().int(), + newDailyRequests: z.number().int(), dailyRequests: z.number().int(), checkHeader: z.string(), fallbackValue: z.number().int(), From f77e5cf8fb9d9babcfc1b3ba046c0ba571489647 Mon Sep 17 00:00:00 2001 From: David Hill <1879069+iamdavidhill@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:52:32 +0000 Subject: [PATCH 065/200] feat(ui): restyle Card and improve tool error cards (#16888) Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> --- packages/ui/src/components/card.css | 103 +++++++++++++--- packages/ui/src/components/card.stories.tsx | 14 +-- packages/ui/src/components/card.tsx | 107 ++++++++++++++++- packages/ui/src/components/markdown.css | 6 + packages/ui/src/components/message-part.css | 36 ------ packages/ui/src/components/message-part.tsx | 21 +--- .../ui/src/components/tool-error-card.css | 54 +++++++++ .../components/tool-error-card.stories.tsx | 96 +++++++++++++++ .../ui/src/components/tool-error-card.tsx | 112 ++++++++++++++++++ packages/ui/src/styles/index.css | 1 + packages/ui/src/styles/theme.css | 4 +- packages/ui/src/theme/themes/oc-2.json | 6 + 12 files changed, 473 insertions(+), 87 deletions(-) create mode 100644 packages/ui/src/components/tool-error-card.css create mode 100644 packages/ui/src/components/tool-error-card.stories.tsx create mode 100644 packages/ui/src/components/tool-error-card.tsx diff --git a/packages/ui/src/components/card.css b/packages/ui/src/components/card.css index 6dae47223d..2d482dba7a 100644 --- a/packages/ui/src/components/card.css +++ b/packages/ui/src/components/card.css @@ -1,29 +1,94 @@ [data-component="card"] { + --card-pad-y: 10px; + --card-pad-r: 12px; + --card-pad-l: 10px; + width: 100%; display: flex; flex-direction: column; - background-color: var(--surface-inset-base); - border: 1px solid var(--border-weaker-base); - transition: background-color 0.15s ease; + position: relative; + background: transparent; + border: none; border-radius: var(--radius-md); - padding: 6px 12px; - overflow: clip; + padding: var(--card-pad-y) var(--card-pad-r) var(--card-pad-y) var(--card-pad-l); - &[data-variant="error"] { - background-color: var(--surface-critical-weak); - border: 1px solid var(--border-critical-base); - color: rgba(218, 51, 25, 0.6); + /* text-14-regular */ + font-family: var(--font-family-sans); + font-size: var(--font-size-base); + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + color: var(--text-strong); - /* text-12-regular */ - font-family: var(--font-family-sans); - font-size: var(--font-size-small); - font-style: normal; - font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); /* 166.667% */ - letter-spacing: var(--letter-spacing-normal); + --card-gap: 8px; + --card-icon: 16px; + --card-indent: 0px; + --card-line-pad: 8px; - &[data-component="icon"] { - color: var(--icon-critical-active); - } + --card-accent: var(--icon-active); + + &:has([data-slot="card-title"]) { + gap: 8px; + } + + &:has([data-slot="card-title-icon"]) { + --card-indent: calc(var(--card-icon) + var(--card-gap)); + } + + &::before { + content: ""; + position: absolute; + left: 0; + top: var(--card-line-pad); + bottom: var(--card-line-pad); + width: 2px; + border-radius: 2px; + background-color: var(--card-accent); + } + + :where([data-card="title"], [data-slot="card-title"]) { + color: var(--text-strong); + font-weight: var(--font-weight-medium); + } + + :where([data-slot="card-title"]) { + display: flex; + align-items: center; + gap: var(--card-gap); + } + + :where([data-slot="card-title"]) [data-component="icon"] { + color: var(--card-accent); + } + + :where([data-slot="card-title-icon"]) { + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--card-icon); + height: var(--card-icon); + flex: 0 0 auto; + } + + :where([data-slot="card-title-icon"][data-placeholder]) [data-component="icon"] { + color: var(--text-weak); + } + + :where([data-slot="card-title-icon"]) + [data-slot="icon-svg"] + :is(path, line, polyline, polygon, rect, circle, ellipse)[stroke] { + stroke-width: 1.5px !important; + } + + :where([data-card="description"], [data-slot="card-description"]) { + color: var(--text-base); + white-space: pre-wrap; + overflow-wrap: anywhere; + word-break: break-word; + } + + :where([data-card="actions"], [data-slot="card-actions"]) { + padding-left: var(--card-indent); } } diff --git a/packages/ui/src/components/card.stories.tsx b/packages/ui/src/components/card.stories.tsx index befb2d34fc..5b9cf3830e 100644 --- a/packages/ui/src/components/card.stories.tsx +++ b/packages/ui/src/components/card.stories.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import { Card } from "./card" +import { Card, CardActions, CardDescription, CardTitle } from "./card" import { Button } from "./button" const docs = `### Overview @@ -49,15 +49,13 @@ export default { render: (props: { variant?: "normal" | "error" | "warning" | "success" | "info" }) => { return ( <Card variant={props.variant}> - <div style={{ display: "flex", alignItems: "center", gap: "12px" }}> - <div style={{ flex: 1 }}> - <div style={{ fontWeight: 500 }}>Card title</div> - <div style={{ color: "var(--text-weak)", fontSize: "13px" }}>Small supporting text.</div> - </div> - <Button size="small" variant="ghost"> + <CardTitle variant={props.variant}>Card title</CardTitle> + <CardDescription>Small supporting text.</CardDescription> + <CardActions> + <Button size="small" variant="secondary"> Action </Button> - </div> + </CardActions> </Card> ) }, diff --git a/packages/ui/src/components/card.tsx b/packages/ui/src/components/card.tsx index 3fb225ab21..7a1bd5e45b 100644 --- a/packages/ui/src/components/card.tsx +++ b/packages/ui/src/components/card.tsx @@ -1,16 +1,57 @@ import { type ComponentProps, splitProps } from "solid-js" +import { Icon, type IconProps } from "./icon" + +type Variant = "normal" | "error" | "warning" | "success" | "info" export interface CardProps extends ComponentProps<"div"> { - variant?: "normal" | "error" | "warning" | "success" | "info" + variant?: Variant +} + +export interface CardTitleProps extends ComponentProps<"div"> { + variant?: Variant + + /** + * Optional title icon. + * + * - `undefined`: picks a default icon based on `variant` (error/warning/success/info) + * - `false`/`null`: disables the icon + * - `Icon` name: forces a specific icon + */ + icon?: IconProps["name"] | false | null +} + +function pick(variant: Variant) { + if (variant === "error") return "circle-ban-sign" as const + if (variant === "warning") return "warning" as const + if (variant === "success") return "circle-check" as const + if (variant === "info") return "help" as const + return +} + +function mix(style: ComponentProps<"div">["style"], value?: string) { + if (!value) return style + if (!style) return { "--card-accent": value } + if (typeof style === "string") return `${style};--card-accent:${value};` + return { ...(style as Record<string, string | number>), "--card-accent": value } } export function Card(props: CardProps) { - const [split, rest] = splitProps(props, ["variant", "class", "classList"]) + const [split, rest] = splitProps(props, ["variant", "style", "class", "classList"]) + const variant = () => split.variant ?? "normal" + const accent = () => { + const v = variant() + if (v === "error") return "var(--icon-critical-base)" + if (v === "warning") return "var(--icon-warning-active)" + if (v === "success") return "var(--icon-success-active)" + if (v === "info") return "var(--icon-info-active)" + return + } return ( <div {...rest} data-component="card" - data-variant={split.variant || "normal"} + data-variant={variant()} + style={mix(split.style, accent())} classList={{ ...(split.classList ?? {}), [split.class ?? ""]: !!split.class, @@ -20,3 +61,63 @@ export function Card(props: CardProps) { </div> ) } + +export function CardTitle(props: CardTitleProps) { + const [split, rest] = splitProps(props, ["variant", "icon", "class", "classList", "children"]) + const show = () => split.icon !== false && split.icon !== null + const name = () => { + if (split.icon === false || split.icon === null) return + if (typeof split.icon === "string") return split.icon + return pick(split.variant ?? "normal") + } + const placeholder = () => !name() + return ( + <div + {...rest} + data-slot="card-title" + classList={{ + ...(split.classList ?? {}), + [split.class ?? ""]: !!split.class, + }} + > + {show() ? ( + <span data-slot="card-title-icon" data-placeholder={placeholder() || undefined}> + <Icon name={name() ?? "dash"} size="small" /> + </span> + ) : null} + {split.children} + </div> + ) +} + +export function CardDescription(props: ComponentProps<"div">) { + const [split, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + <div + {...rest} + data-slot="card-description" + classList={{ + ...(split.classList ?? {}), + [split.class ?? ""]: !!split.class, + }} + > + {split.children} + </div> + ) +} + +export function CardActions(props: ComponentProps<"div">) { + const [split, rest] = splitProps(props, ["class", "classList", "children"]) + return ( + <div + {...rest} + data-slot="card-actions" + classList={{ + ...(split.classList ?? {}), + [split.class ?? ""]: !!split.class, + }} + > + {split.children} + </div> + ) +} diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css index 1fe11a7de8..f82723807d 100644 --- a/packages/ui/src/components/markdown.css +++ b/packages/ui/src/components/markdown.css @@ -60,6 +60,7 @@ ol { margin-top: 0.5rem; margin-bottom: 1rem; + margin-left: 0; padding-left: 1.5rem; list-style-position: outside; } @@ -70,6 +71,7 @@ ol { list-style-type: decimal; + padding-left: 2.25rem; } li { @@ -98,6 +100,10 @@ padding-left: 1rem; /* Minimal indent for nesting only */ } + li > ol { + padding-left: 1.75rem; + } + /* Blockquotes */ blockquote { border-left: 2px solid var(--border-weak-base); diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 704a57e5b0..7ee5378848 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -309,41 +309,6 @@ } } -[data-component="tool-error"] { - display: flex; - align-items: start; - gap: 8px; - - [data-slot="icon-svg"] { - color: var(--icon-critical-base); - margin-top: 4px; - } - - [data-slot="message-part-tool-error-content"] { - display: flex; - align-items: start; - gap: 8px; - } - - [data-slot="message-part-tool-error-title"] { - font-family: var(--font-family-sans); - font-size: var(--font-size-base); - font-style: normal; - font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); - letter-spacing: var(--letter-spacing-normal); - color: var(--text-on-critical-base); - white-space: nowrap; - } - - [data-slot="message-part-tool-error-message"] { - color: var(--text-on-critical-weak); - max-height: 240px; - overflow-y: auto; - word-break: break-word; - } -} - [data-component="tool-output"] { white-space: pre; padding: 0; @@ -717,7 +682,6 @@ [data-component="user-message"] [data-slot="user-message-text"], [data-component="text-part"], [data-component="reasoning-part"], -[data-component="tool-error"], [data-component="tool-output"], [data-component="bash-output"], [data-component="edit-content"], diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 45b174e2b8..0815c33336 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -39,6 +39,7 @@ import { Card } from "./card" import { Collapsible } from "./collapsible" import { FileIcon } from "./file-icon" import { Icon } from "./icon" +import { ToolErrorCard } from "./tool-error-card" import { Checkbox } from "./checkbox" import { DiffChanges } from "./diff-changes" import { Markdown } from "./markdown" @@ -1189,25 +1190,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { </div> ) } - const [title, ...rest] = cleaned.split(": ") - return ( - <Card variant="error"> - <div data-component="tool-error"> - <Icon name="circle-ban-sign" size="small" /> - <Switch> - <Match when={title && title.length < 30}> - <div data-slot="message-part-tool-error-content"> - <div data-slot="message-part-tool-error-title">{title}</div> - <span data-slot="message-part-tool-error-message">{rest.join(": ")}</span> - </div> - </Match> - <Match when={true}> - <span data-slot="message-part-tool-error-message">{cleaned}</span> - </Match> - </Switch> - </div> - </Card> - ) + return <ToolErrorCard tool={part().tool} error={error()} /> }} </Match> <Match when={true}> diff --git a/packages/ui/src/components/tool-error-card.css b/packages/ui/src/components/tool-error-card.css new file mode 100644 index 0000000000..e93484f239 --- /dev/null +++ b/packages/ui/src/components/tool-error-card.css @@ -0,0 +1,54 @@ +[data-component="card"][data-kind="tool-error-card"] { + --card-pad-y: 8px; + --card-line-pad: 12px; + + > [data-component="collapsible"].tool-collapsible { + gap: 0px; + } + + > [data-component="collapsible"].tool-collapsible[data-open="true"] { + gap: 4px; + } + + [data-component="tool-error-card-icon"] [data-component="icon"] { + color: var(--card-accent); + } + + [data-slot="tool-error-card-content"] { + position: relative; + padding-left: 24px; + margin-bottom: 8px; + -webkit-user-select: text; + user-select: text; + } + + > [data-component="collapsible"].tool-collapsible[data-open="true"] [data-slot="tool-error-card-content"] { + padding-right: 40px; + } + + [data-slot="tool-error-card-copy"] { + position: absolute; + top: 0; + right: 0; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; + will-change: opacity; + } + + &:hover [data-slot="tool-error-card-copy"], + &:focus-within [data-slot="tool-error-card-copy"] { + opacity: 1; + pointer-events: auto; + } + + [data-slot="tool-error-card-content"] :where(*)::selection { + background: var(--surface-critical-base); + color: var(--text-on-critical-base); + } + + [data-slot="tool-error-card-content"] :where(*)::-moz-selection { + background: var(--surface-critical-base); + color: var(--text-on-critical-base); + } +} diff --git a/packages/ui/src/components/tool-error-card.stories.tsx b/packages/ui/src/components/tool-error-card.stories.tsx new file mode 100644 index 0000000000..03349ce011 --- /dev/null +++ b/packages/ui/src/components/tool-error-card.stories.tsx @@ -0,0 +1,96 @@ +// @ts-nocheck +import { ToolErrorCard } from "./tool-error-card" + +const docs = `### Overview +Tool call failure summary styled like a tool trigger. + +### API +- Required: \`tool\` (tool id, e.g. apply_patch, bash) +- Required: \`error\` (error string) + +### Behavior +- Collapsible; click header to expand/collapse. +` + +const samples = [ + { + tool: "apply_patch", + error: + "apply_patch verification failed: Failed to find expected lines in /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/session-turn.tsx", + }, + { + tool: "bash", + error: "bash Command failed: exit code 1: bun test --watch", + }, + { + tool: "read", + error: + "read File not found: /Users/davidhill/Documents/Local/opencode/packages/ui/src/components/does-not-exist.tsx", + }, + { + tool: "glob", + error: "glob Pattern error: Invalid glob pattern: **/*[", + }, + { + tool: "grep", + error: "grep Regex error: Invalid regular expression: (unterminated group", + }, + { + tool: "webfetch", + error: "webfetch Request failed: 502 Bad Gateway", + }, + { + tool: "websearch", + error: "websearch Rate limited: Please try again in 30 seconds", + }, + { + tool: "codesearch", + error: "codesearch Timeout: exceeded 120s", + }, + { + tool: "question", + error: "question Dismissed: user dismissed this question", + }, +] + +export default { + title: "UI/ToolErrorCard", + id: "components-tool-error-card", + component: ToolErrorCard, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: docs, + }, + }, + }, + args: { + tool: "apply_patch", + error: samples[0].error, + }, + argTypes: { + tool: { + control: "select", + options: ["apply_patch", "bash", "read", "glob", "grep", "webfetch", "websearch", "codesearch", "question"], + }, + error: { + control: "text", + }, + }, + render: (props: { tool: string; error: string }) => { + return <ToolErrorCard tool={props.tool} error={props.error} /> + }, +} + +export const All = { + render: () => { + return ( + <div style="display: flex; flex-direction: column; gap: 12px; max-width: 720px;"> + {samples.map((item) => ( + <ToolErrorCard tool={item.tool} error={item.error} /> + ))} + </div> + ) + }, +} diff --git a/packages/ui/src/components/tool-error-card.tsx b/packages/ui/src/components/tool-error-card.tsx new file mode 100644 index 0000000000..13873ae90e --- /dev/null +++ b/packages/ui/src/components/tool-error-card.tsx @@ -0,0 +1,112 @@ +import { type ComponentProps, createMemo, createSignal, Show, splitProps } from "solid-js" +import { Card, CardDescription } from "./card" +import { Collapsible } from "./collapsible" +import { Icon } from "./icon" +import { IconButton } from "./icon-button" +import { Tooltip } from "./tooltip" +import { useI18n } from "../context/i18n" + +export interface ToolErrorCardProps extends Omit<ComponentProps<typeof Card>, "children" | "variant"> { + tool: string + error: string +} + +export function ToolErrorCard(props: ToolErrorCardProps) { + const i18n = useI18n() + const [open, setOpen] = createSignal(true) + const [copied, setCopied] = createSignal(false) + const [split, rest] = splitProps(props, ["tool", "error"]) + const name = createMemo(() => { + const map: Record<string, string> = { + read: "ui.tool.read", + list: "ui.tool.list", + glob: "ui.tool.glob", + grep: "ui.tool.grep", + webfetch: "ui.tool.webfetch", + websearch: "ui.tool.websearch", + codesearch: "ui.tool.codesearch", + bash: "ui.tool.shell", + apply_patch: "ui.tool.patch", + question: "ui.tool.questions", + } + const key = map[split.tool] + if (!key) return split.tool + return i18n.t(key) + }) + const cleaned = createMemo(() => split.error.replace(/^Error:\s*/, "").trim()) + const tail = createMemo(() => { + const value = cleaned() + const prefix = `${split.tool} ` + if (value.startsWith(prefix)) return value.slice(prefix.length) + return value + }) + + const subtitle = createMemo(() => { + const parts = tail().split(": ") + if (parts.length <= 1) return "Failed" + const head = (parts[0] ?? "").trim() + if (!head) return "Failed" + return head[0] ? head[0].toUpperCase() + head.slice(1) : "Failed" + }) + + const body = createMemo(() => { + const parts = tail().split(": ") + if (parts.length <= 1) return cleaned() + return parts.slice(1).join(": ").trim() || cleaned() + }) + + const copy = async () => { + const text = cleaned() + if (!text) return + await navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + return ( + <Card {...rest} data-kind="tool-error-card" data-open={open() ? "true" : "false"} variant="error"> + <Collapsible class="tool-collapsible" data-open={open() ? "true" : "false"} open={open()} onOpenChange={setOpen}> + <Collapsible.Trigger> + <div data-component="tool-trigger"> + <div data-slot="basic-tool-tool-trigger-content"> + <span data-slot="basic-tool-tool-indicator" data-component="tool-error-card-icon"> + <Icon name="circle-ban-sign" size="small" style={{ "stroke-width": 1.5 }} /> + </span> + <div data-slot="basic-tool-tool-info"> + <div data-slot="basic-tool-tool-info-structured"> + <div data-slot="basic-tool-tool-info-main"> + <span data-slot="basic-tool-tool-title">{name()}</span> + <span data-slot="basic-tool-tool-subtitle">{subtitle()}</span> + </div> + </div> + </div> + </div> + <Collapsible.Arrow /> + </div> + </Collapsible.Trigger> + <Collapsible.Content> + <div data-slot="tool-error-card-content"> + <Show when={open()}> + <div data-slot="tool-error-card-copy"> + <Tooltip value={copied() ? i18n.t("ui.message.copied") : "Copy error"} placement="top" gutter={4}> + <IconButton + icon={copied() ? "check" : "copy"} + size="normal" + variant="ghost" + onMouseDown={(e) => e.preventDefault()} + onClick={(e) => { + e.stopPropagation() + copy() + }} + aria-label={copied() ? i18n.t("ui.message.copied") : "Copy error"} + /> + </Tooltip> + </div> + </Show> + <Show when={body()}>{(value) => <CardDescription>{value()}</CardDescription>}</Show> + </div> + </Collapsible.Content> + </Collapsible> + </Card> + ) +} diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index cec42f5a0c..1b17f6c2b7 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -13,6 +13,7 @@ @import "../components/basic-tool.css" layer(components); @import "../components/button.css" layer(components); @import "../components/card.css" layer(components); +@import "../components/tool-error-card.css" layer(components); @import "../components/checkbox.css" layer(components); @import "../components/file.css" layer(components); @import "../components/collapsible.css" layer(components); diff --git a/packages/ui/src/styles/theme.css b/packages/ui/src/styles/theme.css index 702d1e4e67..021f959e4c 100644 --- a/packages/ui/src/styles/theme.css +++ b/packages/ui/src/styles/theme.css @@ -131,7 +131,7 @@ --surface-warning-base: #fcf3cb; --surface-warning-weak: #fdfaec; --surface-warning-strong: #fbdd46; - --surface-critical-base: #feefeb; + --surface-critical-base: #fff2f0; --surface-critical-weak: #fff8f6; --surface-critical-strong: #fc533a; --surface-info-base: #fdecfe; @@ -391,7 +391,7 @@ --surface-warning-base: #fdf3cf; --surface-warning-weak: #fdfaed; --surface-warning-strong: #fcd53a; - --surface-critical-base: #42120b; + --surface-critical-base: #1f0603; --surface-critical-weak: #28110c; --surface-critical-strong: #fc533a; --surface-info-base: #feecfe; diff --git a/packages/ui/src/theme/themes/oc-2.json b/packages/ui/src/theme/themes/oc-2.json index fdf0c2caf4..dc413f0610 100644 --- a/packages/ui/src/theme/themes/oc-2.json +++ b/packages/ui/src/theme/themes/oc-2.json @@ -13,6 +13,9 @@ "interactive": "#034cff", "diffAdd": "#9ff29a", "diffDelete": "#fc533a" + }, + "overrides": { + "surface-critical-base": "#FFF2F0" } }, "dark": { @@ -26,6 +29,9 @@ "interactive": "#034cff", "diffAdd": "#c8ffc4", "diffDelete": "#fc533a" + }, + "overrides": { + "surface-critical-base": "#1F0603" } } } From 6b9f8fb9b3ec48859c2db0c230d0cab69f6ae727 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Tue, 10 Mar 2026 15:22:01 -0400 Subject: [PATCH 066/200] zen: raise limit --- .../app/src/routes/zen/util/rateLimiter.ts | 50 ++++++++++++------- packages/console/core/src/subscription.ts | 1 - 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/rateLimiter.ts b/packages/console/app/src/routes/zen/util/rateLimiter.ts index 152ed9b8d1..042ec51f2c 100644 --- a/packages/console/app/src/routes/zen/util/rateLimiter.ts +++ b/packages/console/app/src/routes/zen/util/rateLimiter.ts @@ -17,36 +17,52 @@ export function createRateLimiter( const dict = i18n(localeFromRequest(request)) const limits = Subscription.getFreeLimits() - const limitValue = - limits.checkHeader && !request.headers.get(limits.checkHeader) - ? limits.fallbackValue - : (rateLimit ?? limits.dailyRequests) + const headerExists = request.headers.has(limits.checkHeader) + const dailyLimit = !headerExists ? limits.fallbackValue : (rateLimit ?? limits.dailyRequests) + const isDefaultModel = headerExists && !rateLimit const ip = !rawIp.length ? "unknown" : rawIp const now = Date.now() - const interval = rateLimit ? `${buildYYYYMMDD(now)}${modelId.substring(0, 2)}` : buildYYYYMMDD(now) + const lifetimeInterval = "" + const dailyInterval = rateLimit ? `${buildYYYYMMDD(now)}${modelId.substring(0, 2)}` : buildYYYYMMDD(now) + + let _isNew: boolean return { - track: async () => { - await Database.use((tx) => - tx - .insert(IpRateLimitTable) - .values({ ip, interval, count: 1 }) - .onDuplicateKeyUpdate({ set: { count: sql`${IpRateLimitTable.count} + 1` } }), - ) - }, check: async () => { const rows = await Database.use((tx) => tx .select({ interval: IpRateLimitTable.interval, count: IpRateLimitTable.count }) .from(IpRateLimitTable) - .where(and(eq(IpRateLimitTable.ip, ip), inArray(IpRateLimitTable.interval, [interval]))), + .where( + and( + eq(IpRateLimitTable.ip, ip), + isDefaultModel + ? inArray(IpRateLimitTable.interval, [lifetimeInterval, dailyInterval]) + : inArray(IpRateLimitTable.interval, [dailyInterval]), + ), + ), ) - const total = rows.reduce((sum, r) => sum + r.count, 0) - logger.debug(`rate limit total: ${total}`) - if (total >= limitValue) + const lifetimeCount = rows.find((r) => r.interval === lifetimeInterval)?.count ?? 0 + const dailyCount = rows.find((r) => r.interval === dailyInterval)?.count ?? 0 + logger.debug(`rate limit lifetime: ${lifetimeCount}, daily: ${dailyCount}`) + + _isNew = isDefaultModel && lifetimeCount < dailyLimit * 7 + + if ((_isNew && dailyCount >= dailyLimit * 2) || (!_isNew && dailyCount >= dailyLimit)) throw new FreeUsageLimitError(dict["zen.api.error.rateLimitExceeded"], getRetryAfterDay(now)) }, + track: async () => { + await Database.use((tx) => + tx + .insert(IpRateLimitTable) + .values([ + { ip, interval: dailyInterval, count: 1 }, + ...(_isNew ? [{ ip, interval: lifetimeInterval, count: 1 }] : []), + ]) + .onDuplicateKeyUpdate({ set: { count: sql`${IpRateLimitTable.count} + 1` } }), + ) + }, } } diff --git a/packages/console/core/src/subscription.ts b/packages/console/core/src/subscription.ts index 994feaff0d..9d6c3ce2b5 100644 --- a/packages/console/core/src/subscription.ts +++ b/packages/console/core/src/subscription.ts @@ -8,7 +8,6 @@ export namespace Subscription { const LimitsSchema = z.object({ free: z.object({ promoTokens: z.number().int(), - newDailyRequests: z.number().int(), dailyRequests: z.number().int(), checkHeader: z.string(), fallbackValue: z.number().int(), From 556703f8ab593f391508a75603aed54dc065e3b5 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Tue, 10 Mar 2026 17:17:11 -0400 Subject: [PATCH 067/200] ci: cancel duplicate workflow runs and add read permissions - Add concurrency settings to cancel outdated runs when new commits are pushed - Add contents: read permission for security hardening - Remove redundant required job that checked test results --- .github/workflows/test.yml | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f7b00516f8..d9eded3f17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,14 @@ on: - dev pull_request: workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + jobs: unit: name: unit (${{ matrix.settings.name }}) @@ -86,18 +94,3 @@ jobs: path: | packages/app/e2e/test-results packages/app/e2e/playwright-report - - required: - name: test (linux) - runs-on: blacksmith-4vcpu-ubuntu-2404 - needs: - - unit - - e2e - if: always() - steps: - - name: Verify upstream test jobs passed - run: | - echo "unit=${{ needs.unit.result }}" - echo "e2e=${{ needs.e2e.result }}" - test "${{ needs.unit.result }}" = "success" - test "${{ needs.e2e.result }}" = "success" From 5acfdd1c5d6ff631e708b2fc35d4ece3fe8ab7de Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:20:41 -0500 Subject: [PATCH 068/200] chore: kill old copilot 403 message that was used for old plugin migration (#16904) --- packages/opencode/src/provider/error.ts | 12 -------- .../opencode/test/session/message-v2.test.ts | 29 ------------------- 2 files changed, 41 deletions(-) diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index 945d29f97f..c9987aef45 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -40,14 +40,6 @@ export namespace ProviderError { return /^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message) } - function error(providerID: string, error: APICallError) { - if (providerID.includes("github-copilot") && error.statusCode === 403) { - return "Please reauthenticate with the copilot provider to ensure your credentials work properly with OpenCode." - } - - return error.message - } - function message(providerID: string, e: APICallError) { return iife(() => { const msg = e.message @@ -60,10 +52,6 @@ export namespace ProviderError { return "Unknown error" } - const transformed = error(providerID, e) - if (transformed !== msg) { - return transformed - } if (!e.responseBody || (e.statusCode && msg !== STATUS_CODES[e.statusCode])) { return msg } diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index c043754bdb..184bcd3efa 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -842,35 +842,6 @@ describe("session.message-v2.fromError", () => { }) }) - test("maps github-copilot 403 to reauth guidance", () => { - const error = new APICallError({ - message: "forbidden", - url: "https://api.githubcopilot.com/v1/chat/completions", - requestBodyValues: {}, - statusCode: 403, - responseHeaders: { "content-type": "application/json" }, - responseBody: '{"error":"forbidden"}', - isRetryable: false, - }) - - const result = MessageV2.fromError(error, { providerID: "github-copilot" }) - - expect(result).toStrictEqual({ - name: "APIError", - data: { - message: - "Please reauthenticate with the copilot provider to ensure your credentials work properly with OpenCode.", - statusCode: 403, - isRetryable: false, - responseHeaders: { "content-type": "application/json" }, - responseBody: '{"error":"forbidden"}', - metadata: { - url: "https://api.githubcopilot.com/v1/chat/completions", - }, - }, - }) - }) - test("detects context overflow from APICallError provider messages", () => { const cases = [ "prompt is too long: 213462 tokens > 200000 maximum", From 89bf199c073cbb428a5a1a7d3600e970ba784e18 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 10 Mar 2026 19:03:44 -0500 Subject: [PATCH 069/200] chore(app): fix tests --- packages/app/e2e/prompt/prompt-slash-terminal.spec.ts | 6 ++---- packages/app/e2e/selectors.ts | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts index eefce19dc0..bf9f96b47f 100644 --- a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts +++ b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts @@ -9,14 +9,12 @@ test("/terminal toggles the terminal panel", async ({ page, gotoSession }) => { await expect(terminal).not.toBeVisible() - await prompt.click() - await page.keyboard.type("/terminal") + await prompt.fill("/terminal") await expect(page.locator('[data-slash-id="terminal.toggle"]').first()).toBeVisible() await page.keyboard.press("Enter") await expect(terminal).toBeVisible() - await prompt.click() - await page.keyboard.type("/terminal") + await prompt.fill("/terminal") await expect(page.locator('[data-slash-id="terminal.toggle"]').first()).toBeVisible() await page.keyboard.press("Enter") await expect(terminal).not.toBeVisible() diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 2061a11284..64b7bfe545 100644 --- a/packages/app/e2e/selectors.ts +++ b/packages/app/e2e/selectors.ts @@ -1,5 +1,6 @@ export const promptSelector = '[data-component="prompt-input"]' -export const terminalSelector = '[data-component="terminal"]' +export const terminalPanelSelector = '#terminal-panel[aria-hidden="false"]' +export const terminalSelector = `${terminalPanelSelector} [data-component="terminal"]` export const sessionComposerDockSelector = '[data-component="session-prompt-dock"]' export const questionDockSelector = '[data-component="dock-prompt"][data-kind="question"]' export const permissionDockSelector = '[data-component="dock-prompt"][data-kind="permission"]' From 1b6820bab5e12abf695b6283f319c8da92a537d9 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Tue, 10 Mar 2026 20:13:46 -0400 Subject: [PATCH 070/200] sync --- .opencode/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.opencode/.gitignore b/.opencode/.gitignore index 00bfdfda29..03445edaf2 100644 --- a/.opencode/.gitignore +++ b/.opencode/.gitignore @@ -1,3 +1,4 @@ plans/ bun.lock package.json +package-lock.json From a1b06d63c93e9a7cc5387c8fbaac8ea5621a34b6 Mon Sep 17 00:00:00 2001 From: Kit Langton <kit.langton@gmail.com> Date: Tue, 10 Mar 2026 20:39:07 -0400 Subject: [PATCH 071/200] fix(account): resilient orgs fetch (#16944) --- packages/opencode/src/account/service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/account/service.ts b/packages/opencode/src/account/service.ts index c57bb459a0..ab1de72557 100644 --- a/packages/opencode/src/account/service.ts +++ b/packages/opencode/src/account/service.ts @@ -192,11 +192,17 @@ export class AccountService extends ServiceMap.Service< const orgsByAccount = Effect.fn("AccountService.orgsByAccount")(function* () { const accounts = yield* repo.list() - return yield* Effect.forEach( + const [errors, results] = yield* Effect.partition( accounts, (account) => orgs(account.id).pipe(Effect.map((orgs) => ({ account, orgs }))), { concurrency: 3 }, ) + for (const error of errors) { + yield* Effect.logWarning("failed to fetch orgs for account").pipe( + Effect.annotateLogs({ error: String(error) }), + ) + } + return results }) const orgs = Effect.fn("AccountService.orgs")(function* (accountID: AccountID) { From 0843964eb31fc355de5b6132e6d20beadb31a4b4 Mon Sep 17 00:00:00 2001 From: Jack <jack@anoma.ly> Date: Wed, 11 Mar 2026 11:07:13 +0800 Subject: [PATCH 072/200] feat(web): use Feishu for Chinese community links (#16908) Co-authored-by: Frank <frank@anoma.ly> --- README.zh.md | 2 +- README.zht.md | 2 +- packages/console/app/src/component/footer.tsx | 8 +++++++- packages/console/app/src/i18n/en.ts | 1 + packages/console/app/src/i18n/zh.ts | 1 + packages/console/app/src/i18n/zht.ts | 1 + packages/console/app/src/routes/feishu.ts | 7 +++++++ 7 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 packages/console/app/src/routes/feishu.ts diff --git a/README.zh.md b/README.zh.md index b11d9857c9..0859ed11d0 100644 --- a/README.zh.md +++ b/README.zh.md @@ -137,4 +137,4 @@ OpenCode 内置两种 Agent,可用 `Tab` 键快速切换: --- -**加入我们的社区** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode) +**加入我们的社区** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true) | [X.com](https://x.com/opencode) diff --git a/README.zht.md b/README.zht.md index 573ca85ab4..b7d8b8fc47 100644 --- a/README.zht.md +++ b/README.zht.md @@ -137,4 +137,4 @@ OpenCode 內建了兩種 Agent,您可以使用 `Tab` 鍵快速切換。 --- -**加入我們的社群** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode) +**加入我們的社群** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true) | [X.com](https://x.com/opencode) diff --git a/packages/console/app/src/component/footer.tsx b/packages/console/app/src/component/footer.tsx index d81bf32476..0ea370ac78 100644 --- a/packages/console/app/src/component/footer.tsx +++ b/packages/console/app/src/component/footer.tsx @@ -8,6 +8,12 @@ import { useI18n } from "~/context/i18n" export function Footer() { const language = useLanguage() const i18n = useI18n() + const community = createMemo(() => { + const locale = language.locale() + return locale === "zh" || locale === "zht" + ? ({ key: "footer.feishu", link: language.route("/feishu") } as const) + : ({ key: "footer.discord", link: language.route("/discord") } as const) + }) const githubData = createAsync(() => github()) const starCount = createMemo(() => githubData()?.stars @@ -32,7 +38,7 @@ export function Footer() { <a href={language.route("/changelog")}>{i18n.t("footer.changelog")}</a> </div> <div data-slot="cell"> - <a href={language.route("/discord")}>{i18n.t("footer.discord")}</a> + <a href={community().link}>{i18n.t(community().key)}</a> </div> <div data-slot="cell"> <a href={config.social.twitter}>{i18n.t("footer.x")}</a> diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 2a279757b3..02d6126c4a 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -21,6 +21,7 @@ export const dict = { "footer.github": "GitHub", "footer.docs": "Docs", "footer.changelog": "Changelog", + "footer.feishu": "Feishu", "footer.discord": "Discord", "footer.x": "X", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 87ba1b2450..e12fe77494 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -24,6 +24,7 @@ export const dict = { "footer.github": "GitHub", "footer.docs": "文档", "footer.changelog": "更新日志", + "footer.feishu": "飞书", "footer.discord": "Discord", "footer.x": "X", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index b3f1db0124..b45f871566 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -24,6 +24,7 @@ export const dict = { "footer.github": "GitHub", "footer.docs": "文件", "footer.changelog": "更新日誌", + "footer.feishu": "飞书", "footer.discord": "Discord", "footer.x": "X", diff --git a/packages/console/app/src/routes/feishu.ts b/packages/console/app/src/routes/feishu.ts new file mode 100644 index 0000000000..35d2fcb0e2 --- /dev/null +++ b/packages/console/app/src/routes/feishu.ts @@ -0,0 +1,7 @@ +import { redirect } from "@solidjs/router" + +export async function GET() { + return redirect( + "https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true", + ) +} From b4ae030fc2395887bac8df072627366c6f2c269c Mon Sep 17 00:00:00 2001 From: SOUMITRA-SAHA <soumitrosahaofficial@gmail.com> Date: Wed, 11 Mar 2026 09:02:39 +0530 Subject: [PATCH 073/200] fix: add GOOGLE_VERTEX_LOCATION env var support for Vertex AI (#16922) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/provider/provider.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index c174ebd9ff..ccd3c55b4f 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -67,7 +67,11 @@ export namespace Provider { const project = options["project"] ?? Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT") const location = - options["location"] ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1" + options["location"] ?? + Env.get("GOOGLE_VERTEX_LOCATION") ?? + Env.get("GOOGLE_CLOUD_LOCATION") ?? + Env.get("VERTEX_LOCATION") ?? + "us-central1" const endpoint = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com` return { @@ -437,7 +441,11 @@ export namespace Provider { Env.get("GCLOUD_PROJECT") const location = - provider.options?.location ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1" + provider.options?.location ?? + Env.get("GOOGLE_VERTEX_LOCATION") ?? + Env.get("GOOGLE_CLOUD_LOCATION") ?? + Env.get("VERTEX_LOCATION") ?? + "us-central1" const autoload = Boolean(project) if (!autoload) return { autoload: false } From 4ab35d2c5c92ccbd88dea65e8c2e0f6af748b884 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 11 Mar 2026 13:33:06 +1000 Subject: [PATCH 074/200] fix(electron): hide Windows background consoles (#16842) Co-authored-by: Brendan Allan <git@brendonovich.dev> --- packages/desktop-electron/src/main/cli.ts | 4 ++-- packages/opencode/src/lsp/index.ts | 1 + packages/opencode/src/lsp/server.ts | 7 ++++++- packages/opencode/src/session/prompt.ts | 1 + packages/opencode/src/shell/shell.ts | 5 ++++- packages/opencode/src/tool/bash.ts | 1 + packages/opencode/src/util/process.ts | 1 + 7 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/desktop-electron/src/main/cli.ts b/packages/desktop-electron/src/main/cli.ts index e338d39138..fba301f36c 100644 --- a/packages/desktop-electron/src/main/cli.ts +++ b/packages/desktop-electron/src/main/cli.ts @@ -107,7 +107,7 @@ export function syncCli() { let version = "" try { - version = execFileSync(installPath, ["--version"]).toString().trim() + version = execFileSync(installPath, ["--version"], { windowsHide: true }).toString().trim() } catch { return } @@ -147,7 +147,7 @@ export function spawnCommand(args: string, extraEnv: Record<string, string>) { console.log(`[cli] Executing: ${cmd} ${cmdArgs.join(" ")}`) const child = spawn(cmd, cmdArgs, { env: envs, - detached: true, + detached: process.platform !== "win32", windowsHide: true, stdio: ["ignore", "pipe", "pipe"], }) diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index 9d7d30632a..6ea7554c09 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -114,6 +114,7 @@ export namespace LSP { return { process: spawn(item.command[0], item.command.slice(1), { cwd: root, + windowsHide: true, env: { ...process.env, ...item.env, diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 71f50c94b1..c918008b44 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1,4 +1,4 @@ -import { spawn, type ChildProcessWithoutNullStreams } from "child_process" +import { spawn as launch, type ChildProcessWithoutNullStreams } from "child_process" import path from "path" import os from "os" import { Global } from "../global" @@ -14,6 +14,11 @@ import { Process } from "../util/process" import { which } from "../util/which" import { Module } from "@opencode-ai/util/module" +const spawn = ((cmd, args, opts) => { + if (Array.isArray(args)) return launch(cmd, [...args], { ...(opts ?? {}), windowsHide: true }); + return launch(cmd, { ...(args ?? {}), windowsHide: true }); +}) as typeof launch + export namespace LSPServer { const log = Log.create({ service: "lsp.server" }) const pathExists = async (p: string) => diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 7698b78bab..655afd2b14 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1629,6 +1629,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const proc = spawn(shell, args, { cwd, detached: process.platform !== "win32", + windowsHide: process.platform === "win32", stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, diff --git a/packages/opencode/src/shell/shell.ts b/packages/opencode/src/shell/shell.ts index 60ae46f5ee..a30889d699 100644 --- a/packages/opencode/src/shell/shell.ts +++ b/packages/opencode/src/shell/shell.ts @@ -15,7 +15,10 @@ export namespace Shell { if (process.platform === "win32") { await new Promise<void>((resolve) => { - const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], { stdio: "ignore" }) + const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], { + stdio: "ignore", + windowsHide: true, + }) killer.once("exit", () => resolve()) killer.once("error", () => resolve()) }) diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 3be2734863..109a665363 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -173,6 +173,7 @@ export const BashTool = Tool.define("bash", async () => { }, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32", + windowsHide: process.platform === "win32", }) let output = "" diff --git a/packages/opencode/src/util/process.ts b/packages/opencode/src/util/process.ts index 473ee27dc9..0490969370 100644 --- a/packages/opencode/src/util/process.ts +++ b/packages/opencode/src/util/process.ts @@ -60,6 +60,7 @@ export namespace Process { cwd: opts.cwd, env: opts.env === null ? {} : opts.env ? { ...process.env, ...opts.env } : undefined, stdio: [opts.stdin ?? "ignore", opts.stdout ?? "ignore", opts.stderr ?? "ignore"], + windowsHide: process.platform === "win32", }) let closed = false From 7ec398d8555ca36afbbe2dea450eb6f57d04f16b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:34:02 +0000 Subject: [PATCH 075/200] chore: generate --- packages/opencode/src/lsp/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index c918008b44..8f93213ea1 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -15,8 +15,8 @@ import { which } from "../util/which" import { Module } from "@opencode-ai/util/module" const spawn = ((cmd, args, opts) => { - if (Array.isArray(args)) return launch(cmd, [...args], { ...(opts ?? {}), windowsHide: true }); - return launch(cmd, { ...(args ?? {}), windowsHide: true }); + if (Array.isArray(args)) return launch(cmd, [...args], { ...(opts ?? {}), windowsHide: true }) + return launch(cmd, { ...(args ?? {}), windowsHide: true }) }) as typeof launch export namespace LSPServer { From 802416639b9ef55220340db2a82dde4ddf7ff837 Mon Sep 17 00:00:00 2001 From: Brendan Allan <git@brendonovich.dev> Date: Wed, 11 Mar 2026 16:09:17 +0800 Subject: [PATCH 076/200] ci: setup node in tauri build --- .github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0dbd04f821..079e6d6f09 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -149,6 +149,10 @@ jobs: - uses: ./.github/actions/setup-bun + - uses: actions/setup-node@v4 + with: + node-version: "24" + - name: Cache apt packages if: contains(matrix.settings.host, 'ubuntu') uses: actions/cache@v4 From db57fe6193322941f71b11c5b0ccb8f03d085804 Mon Sep 17 00:00:00 2001 From: Filip <34747899+neriousy@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:22:33 +0100 Subject: [PATCH 077/200] fix(app): make error tool card respect settings (#17005) --- packages/ui/src/components/message-part.tsx | 2 +- packages/ui/src/components/tool-error-card.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 0815c33336..afe4109e15 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -1190,7 +1190,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { </div> ) } - return <ToolErrorCard tool={part().tool} error={error()} /> + return <ToolErrorCard tool={part().tool} error={error()} defaultOpen={props.defaultOpen} /> }} </Match> <Match when={true}> diff --git a/packages/ui/src/components/tool-error-card.tsx b/packages/ui/src/components/tool-error-card.tsx index 13873ae90e..2e9612b2b3 100644 --- a/packages/ui/src/components/tool-error-card.tsx +++ b/packages/ui/src/components/tool-error-card.tsx @@ -9,13 +9,14 @@ import { useI18n } from "../context/i18n" export interface ToolErrorCardProps extends Omit<ComponentProps<typeof Card>, "children" | "variant"> { tool: string error: string + defaultOpen?: boolean } export function ToolErrorCard(props: ToolErrorCardProps) { const i18n = useI18n() - const [open, setOpen] = createSignal(true) + const [open, setOpen] = createSignal(props.defaultOpen ?? false) const [copied, setCopied] = createSignal(false) - const [split, rest] = splitProps(props, ["tool", "error"]) + const [split, rest] = splitProps(props, ["tool", "error", "defaultOpen"]) const name = createMemo(() => { const map: Record<string, string> = { read: "ui.tool.read", From 7291e282738a3745330e20180371bd7c6fb11e5b Mon Sep 17 00:00:00 2001 From: Shoubhit Dash <shoubhit2005@gmail.com> Date: Wed, 11 Mar 2026 18:19:17 +0530 Subject: [PATCH 078/200] perf(app): trim session render work (#16987) --- .../src/pages/session/message-timeline.tsx | 1 + packages/ui/src/components/message-part.tsx | 36 +++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 6463e7cbbe..223539092f 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -764,6 +764,7 @@ export function MessageTimeline(props: { "min-w-0 w-full max-w-full": true, "md:max-w-200 2xl:max-w-[1000px]": props.centered, }} + style={{ "content-visibility": "auto", "contain-intrinsic-size": "auto 500px" }} > <Show when={commentCount() > 0}> <div class="w-full px-4 md:px-5 pb-2"> diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index afe4109e15..65b6229d6f 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -437,8 +437,8 @@ function groupParts(parts: { messageID: string; part: PartType }[]) { return result } -function partByID(parts: readonly PartType[], partID: string) { - return parts.find((part) => part.id === partID) +function index<T extends { id: string }>(items: readonly T[]) { + return new Map(items.map((item) => [item.id, item] as const)) } function renderable(part: PartType, showReasoningSummaries = true) { @@ -474,6 +474,13 @@ export function AssistantParts(props: { const data = useData() const emptyParts: PartType[] = [] const emptyTools: ToolPart[] = [] + const msgs = createMemo(() => index(props.messages)) + const part = createMemo( + () => + new Map( + props.messages.map((message) => [message.id, index(list(data.store.part?.[message.id], emptyParts))] as const), + ), + ) const grouped = createMemo( () => @@ -507,7 +514,7 @@ export function AssistantParts(props: { const entry = entryAccessor() if (entry.type !== "context") return emptyTools return entry.refs - .map((ref) => partByID(list(data.store.part?.[ref.messageID], emptyParts), ref.partID)) + .map((ref) => part().get(ref.messageID)?.get(ref.partID)) .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) }, emptyTools, @@ -527,23 +534,23 @@ export function AssistantParts(props: { const message = createMemo(() => { const entry = entryAccessor() if (entry.type !== "part") return - return props.messages.find((item) => item.id === entry.ref.messageID) + return msgs().get(entry.ref.messageID) }) - const part = createMemo(() => { + const item = createMemo(() => { const entry = entryAccessor() if (entry.type !== "part") return - return partByID(list(data.store.part?.[entry.ref.messageID], emptyParts), entry.ref.partID) + return part().get(entry.ref.messageID)?.get(entry.ref.partID) }) return ( <Show when={message()}> - <Show when={part()}> + <Show when={item()}> <Part - part={part()!} + part={item()!} message={message()!} showAssistantCopyPartID={props.showAssistantCopyPartID} turnDurationMs={props.turnDurationMs} - defaultOpen={partDefaultOpen(part()!, props.shellToolDefaultOpen, props.editToolDefaultOpen)} + defaultOpen={partDefaultOpen(item()!, props.shellToolDefaultOpen, props.editToolDefaultOpen)} /> </Show> </Show> @@ -695,6 +702,7 @@ export function AssistantMessageDisplay(props: { showReasoningSummaries?: boolean }) { const emptyTools: ToolPart[] = [] + const part = createMemo(() => index(props.parts)) const grouped = createMemo( () => groupParts( @@ -723,7 +731,7 @@ export function AssistantMessageDisplay(props: { const entry = entryAccessor() if (entry.type !== "context") return emptyTools return entry.refs - .map((ref) => partByID(props.parts, ref.partID)) + .map((ref) => part().get(ref.partID)) .filter((part): part is ToolPart => !!part && isContextGroupTool(part)) }, emptyTools, @@ -739,16 +747,16 @@ export function AssistantMessageDisplay(props: { </Match> <Match when={entryType() === "part"}> {(() => { - const part = createMemo(() => { + const item = createMemo(() => { const entry = entryAccessor() if (entry.type !== "part") return - return partByID(props.parts, entry.ref.partID) + return part().get(entry.ref.partID) }) return ( - <Show when={part()}> + <Show when={item()}> <Part - part={part()!} + part={item()!} message={props.message} showAssistantCopyPartID={props.showAssistantCopyPartID} /> From 0f6bc8ae71d18645212cb954bd210f047b5416d8 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:24:55 -0500 Subject: [PATCH 079/200] tweak: adjust way skills are presented to agent to increase likelyhood of skill invocations. (#17053) --- packages/opencode/src/session/prompt.ts | 7 ++++++- packages/opencode/src/session/system.ts | 16 ++++++++++++++ packages/opencode/src/skill/skill.ts | 23 ++++++++++++++++++++ packages/opencode/src/tool/skill.ts | 28 +++++-------------------- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 655afd2b14..54adf1104a 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -650,7 +650,12 @@ export namespace SessionPrompt { await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) // Build system prompt, adding structured output instruction if needed - const system = [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system())] + const skills = await SystemPrompt.skills(agent) + const system = [ + ...(await SystemPrompt.environment(model)), + ...(skills ? [skills] : []), + ...(await InstructionPrompt.system()), + ] const format = lastUser.format ?? { type: "text" } if (format.type === "json_schema") { system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT) diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index a61dd8cba5..0f0f6b51b3 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -10,6 +10,9 @@ import PROMPT_GEMINI from "./prompt/gemini.txt" import PROMPT_CODEX from "./prompt/codex_header.txt" import PROMPT_TRINITY from "./prompt/trinity.txt" import type { Provider } from "@/provider/provider" +import type { Agent } from "@/agent/agent" +import { PermissionNext } from "@/permission/next" +import { Skill } from "@/skill" export namespace SystemPrompt { export function instructions() { @@ -34,6 +37,7 @@ export namespace SystemPrompt { `Here is some useful information about the environment you are running in:`, `<env>`, ` Working directory: ${Instance.directory}`, + ` Workspace root folder: ${Instance.worktree}`, ` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`, ` Platform: ${process.platform}`, ` Today's date: ${new Date().toDateString()}`, @@ -51,4 +55,16 @@ export namespace SystemPrompt { ].join("\n"), ] } + + export async function skills(agent: Agent.Info) { + if (PermissionNext.disabled(["skill"], agent.permission).has("skill")) return + + const list = await Skill.available(agent) + + return [ + "Skills provide specialized instructions and workflows for specific tasks.", + "Use the skill tool to load a skill when a task matches its description.", + list.length === 0 ? "No skills are currently available." : "\n" + Skill.fmt(list), + ].join("\n") + } } diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index c474c94dd7..09cc787c80 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -13,6 +13,9 @@ import { Bus } from "@/bus" import { Session } from "@/session" import { Discovery } from "./discovery" import { Glob } from "../util/glob" +import { pathToFileURL } from "url" +import type { Agent } from "@/agent/agent" +import { PermissionNext } from "@/permission/next" export namespace Skill { const log = Log.create({ service: "skill" }) @@ -186,4 +189,24 @@ export namespace Skill { export async function dirs() { return state().then((x) => x.dirs) } + + export async function available(agent?: Agent.Info) { + const list = await all() + if (!agent) return list + return list.filter((skill) => PermissionNext.evaluate("skill", skill.name, agent.permission).action !== "deny") + } + + export function fmt(list: Info[]) { + return [ + "<available_skills>", + ...list.flatMap((skill) => [ + ` <skill>`, + ` <name>${skill.name}</name>`, + ` <description>${skill.description}</description>`, + ` <location>${pathToFileURL(skill.location).href}</location>`, + ` </skill>`, + ]), + "</available_skills>", + ].join("\n") + } } diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts index 8fcfb592de..6d2a48b0ed 100644 --- a/packages/opencode/src/tool/skill.ts +++ b/packages/opencode/src/tool/skill.ts @@ -3,24 +3,14 @@ import { pathToFileURL } from "url" import z from "zod" import { Tool } from "./tool" import { Skill } from "../skill" -import { PermissionNext } from "../permission/next" import { Ripgrep } from "../file/ripgrep" import { iife } from "@/util/iife" export const SkillTool = Tool.define("skill", async (ctx) => { - const skills = await Skill.all() - - // Filter skills by agent permissions if agent provided - const agent = ctx?.agent - const accessibleSkills = agent - ? skills.filter((skill) => { - const rule = PermissionNext.evaluate("skill", skill.name, agent.permission) - return rule.action !== "deny" - }) - : skills + const list = await Skill.available(ctx?.agent) const description = - accessibleSkills.length === 0 + list.length === 0 ? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available." : [ "Load a specialized skill that provides domain-specific instructions and workflows.", @@ -34,18 +24,10 @@ export const SkillTool = Tool.define("skill", async (ctx) => { "The following skills provide specialized sets of instructions for particular tasks", "Invoke this tool to load a skill when a task matches one of the available skills listed below:", "", - "<available_skills>", - ...accessibleSkills.flatMap((skill) => [ - ` <skill>`, - ` <name>${skill.name}</name>`, - ` <description>${skill.description}</description>`, - ` <location>${pathToFileURL(skill.location).href}</location>`, - ` </skill>`, - ]), - "</available_skills>", + Skill.fmt(list), ].join("\n") - const examples = accessibleSkills + const examples = list .map((skill) => `'${skill.name}'`) .slice(0, 3) .join(", ") @@ -62,7 +44,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => { const skill = await Skill.get(params.name) if (!skill) { - const available = await Skill.all().then((x) => Object.keys(x).join(", ")) + const available = await Skill.all().then((x) => x.map((skill) => skill.name).join(", ")) throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`) } From 9c585bb58ba98826cd5f7bf596cb65f411d378a4 Mon Sep 17 00:00:00 2001 From: xinxin <45682184+andyWang1688@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:35:16 +0800 Subject: [PATCH 080/200] docs(providers): clarify npm choice for chat vs responses APIs (#16974) Co-authored-by: wangxinxin <xinxin.wang@pharmbrain.com> --- packages/web/src/content/docs/providers.mdx | 4 ++-- packages/web/src/content/docs/zh-cn/providers.mdx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 34e3626499..7f99320519 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -1890,7 +1890,7 @@ You can use any OpenAI-compatible provider with opencode. Most modern AI provide ``` Here are the configuration options: - - **npm**: AI SDK package to use, `@ai-sdk/openai-compatible` for OpenAI-compatible providers + - **npm**: AI SDK package to use, `@ai-sdk/openai-compatible` for OpenAI-compatible providers (for `/v1/chat/completions`). If your provider/model uses `/v1/responses`, use `@ai-sdk/openai`. - **name**: Display name in UI. - **models**: Available models. - **options.baseURL**: API endpoint URL. @@ -1957,5 +1957,5 @@ If you are having trouble with configuring a provider, check the following: 2. For custom providers, check the opencode config and: - Make sure the provider ID used in the `/connect` command matches the ID in your opencode config. - - The right npm package is used for the provider. For example, use `@ai-sdk/cerebras` for Cerebras. And for all other OpenAI-compatible providers, use `@ai-sdk/openai-compatible`. + - The right npm package is used for the provider. For example, use `@ai-sdk/cerebras` for Cerebras. And for all other OpenAI-compatible providers, use `@ai-sdk/openai-compatible` (for `/v1/chat/completions`); if a model uses `/v1/responses`, use `@ai-sdk/openai`. For mixed setups under one provider, you can override per model via `provider.npm`. - Check correct API endpoint is used in the `options.baseURL` field. diff --git a/packages/web/src/content/docs/zh-cn/providers.mdx b/packages/web/src/content/docs/zh-cn/providers.mdx index 9c1616876d..571b730242 100644 --- a/packages/web/src/content/docs/zh-cn/providers.mdx +++ b/packages/web/src/content/docs/zh-cn/providers.mdx @@ -1845,7 +1845,7 @@ Vercel AI Gateway 允许你通过统一端点访问来自 OpenAI、Anthropic、G ``` 以下是配置选项说明: - - **npm**:要使用的 AI SDK 包,对于 OpenAI 兼容的提供商使用 `@ai-sdk/openai-compatible` + - **npm**:要使用的 AI SDK 包,对于 OpenAI 兼容的提供商使用 `@ai-sdk/openai-compatible`(适用于 `/v1/chat/completions`)。如果你的提供商/模型走 `/v1/responses`,请使用 `@ai-sdk/openai`。 - **name**:在 UI 中显示的名称。 - **models**:可用模型。 - **options.baseURL**:API 端点 URL。 @@ -1911,5 +1911,5 @@ Vercel AI Gateway 允许你通过统一端点访问来自 OpenAI、Anthropic、G 2. 对于自定义提供商,请检查 OpenCode 配置并确认: - `/connect` 命令中使用的提供商 ID 与 OpenCode 配置中的 ID 一致。 - - 使用了正确的 npm 包。例如,Cerebras 应使用 `@ai-sdk/cerebras`。对于其他所有 OpenAI 兼容的提供商,使用 `@ai-sdk/openai-compatible`。 + - 使用了正确的 npm 包。例如,Cerebras 应使用 `@ai-sdk/cerebras`。对于其他所有 OpenAI 兼容的提供商,使用 `@ai-sdk/openai-compatible`(`/v1/chat/completions`);如果模型走 `/v1/responses`,请使用 `@ai-sdk/openai`。同一 provider 混用时,可在模型下设置 `provider.npm` 覆盖默认值。 - `options.baseURL` 字段中的 API 端点地址正确。 From bcc0d198678f9e88c1868bda2e7f6e54768117fe Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Mar 2026 12:24:51 -0500 Subject: [PATCH 081/200] chore(app): simplify review pane (#17066) --- .../app/e2e/session/session-review.spec.ts | 186 +++++++++ packages/app/src/pages/session.tsx | 141 +++---- packages/ui/src/components/file.tsx | 147 ++----- .../components/session-review-search.test.ts | 39 -- .../src/components/session-review-search.ts | 59 --- packages/ui/src/components/session-review.tsx | 376 +----------------- packages/ui/src/pierre/file-find.ts | 180 ++------- 7 files changed, 319 insertions(+), 809 deletions(-) create mode 100644 packages/app/e2e/session/session-review.spec.ts delete mode 100644 packages/ui/src/components/session-review-search.test.ts delete mode 100644 packages/ui/src/components/session-review-search.ts diff --git a/packages/app/e2e/session/session-review.spec.ts b/packages/app/e2e/session/session-review.spec.ts new file mode 100644 index 0000000000..4198c733c0 --- /dev/null +++ b/packages/app/e2e/session/session-review.spec.ts @@ -0,0 +1,186 @@ +import { waitSessionIdle, withSession } from "../actions" +import { test, expect } from "../fixtures" +import { createSdk } from "../utils" + +const count = 14 + +function body(mark: string) { + return [ + `title ${mark}`, + `mark ${mark}`, + ...Array.from({ length: 32 }, (_, i) => `line ${String(i + 1).padStart(2, "0")} ${mark}`), + ] +} + +function files(tag: string) { + return Array.from({ length: count }, (_, i) => { + const id = String(i).padStart(2, "0") + return { + file: `review-scroll-${id}.txt`, + mark: `${tag}-${id}`, + } + }) +} + +function seed(list: ReturnType<typeof files>) { + const out = ["*** Begin Patch"] + + for (const item of list) { + out.push(`*** Add File: ${item.file}`) + for (const line of body(item.mark)) out.push(`+${line}`) + } + + out.push("*** End Patch") + return out.join("\n") +} + +function edit(file: string, prev: string, next: string) { + return ["*** Begin Patch", `*** Update File: ${file}`, "@@", `-mark ${prev}`, `+mark ${next}`, "*** End Patch"].join( + "\n", + ) +} + +async function patch(sdk: ReturnType<typeof createSdk>, sessionID: string, patchText: string) { + await sdk.session.promptAsync({ + sessionID, + agent: "build", + system: [ + "You are seeding deterministic e2e UI state.", + "Your only valid response is one apply_patch tool call.", + `Use this JSON input: ${JSON.stringify({ patchText })}`, + "Do not call any other tools.", + "Do not output plain text.", + ].join("\n"), + parts: [{ type: "text", text: "Apply the provided patch exactly once." }], + }) + + await waitSessionIdle(sdk, sessionID, 120_000) +} + +async function show(page: Parameters<typeof test>[0]["page"]) { + const btn = page.getByRole("button", { name: "Toggle review" }).first() + await expect(btn).toBeVisible() + if ((await btn.getAttribute("aria-expanded")) !== "true") await btn.click() + await expect(btn).toHaveAttribute("aria-expanded", "true") +} + +async function expand(page: Parameters<typeof test>[0]["page"]) { + const close = page.getByRole("button", { name: /^Collapse all$/i }).first() + const open = await close + .isVisible() + .then((value) => value) + .catch(() => false) + + const btn = page.getByRole("button", { name: /^Expand all$/i }).first() + if (open) { + await close.click() + await expect(btn).toBeVisible() + } + + await expect(btn).toBeVisible() + await btn.click() + await expect(close).toBeVisible() +} + +async function waitMark(page: Parameters<typeof test>[0]["page"], file: string, mark: string) { + await page.waitForFunction( + ({ file, mark }) => { + const head = Array.from(document.querySelectorAll("h3")).find( + (node) => node instanceof HTMLElement && node.textContent?.includes(file), + ) + if (!(head instanceof HTMLElement)) return false + + return Array.from(head.parentElement?.querySelectorAll("diffs-container") ?? []).some((host) => { + if (!(host instanceof HTMLElement)) return false + const root = host.shadowRoot + return root?.textContent?.includes(`mark ${mark}`) ?? false + }) + }, + { file, mark }, + { timeout: 60_000 }, + ) +} + +test("review keeps scroll position after a live diff update", async ({ page, withProject }) => { + test.setTimeout(180_000) + + const tag = `review-${Date.now()}` + const list = files(tag) + const hit = list[list.length - 2]! + const next = `${tag}-live` + + await page.setViewportSize({ width: 1600, height: 1000 }) + + await withProject(async (project) => { + const sdk = createSdk(project.directory) + + await withSession(sdk, `e2e review ${tag}`, async (session) => { + await patch(sdk, session.id, seed(list)) + + await expect + .poll( + async () => { + const info = await sdk.session.get({ sessionID: session.id }).then((res) => res.data) + return info?.summary?.files ?? 0 + }, + { timeout: 60_000 }, + ) + .toBe(list.length) + + await expect + .poll( + async () => { + const diff = await sdk.session.diff({ sessionID: session.id }).then((res) => res.data ?? []) + return diff.length + }, + { timeout: 60_000 }, + ) + .toBe(list.length) + + await project.gotoSession(session.id) + await show(page) + + const tab = page.getByRole("tab", { name: /Review/i }).first() + await expect(tab).toBeVisible() + await tab.click() + + const view = page.locator('[data-slot="session-review-scroll"] .scroll-view__viewport').first() + await expect(view).toBeVisible() + const heads = page.getByRole("heading", { level: 3 }).filter({ hasText: /^review-scroll-/ }) + await expect(heads).toHaveCount(list.length, { + timeout: 60_000, + }) + + await expand(page) + await waitMark(page, hit.file, hit.mark) + + const row = page + .getByRole("heading", { level: 3, name: new RegExp(hit.file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }) + .first() + await expect(row).toBeVisible() + await row.evaluate((el) => el.scrollIntoView({ block: "center" })) + + await expect.poll(() => view.evaluate((el) => el.scrollTop)).toBeGreaterThan(200) + const prev = await view.evaluate((el) => el.scrollTop) + + await patch(sdk, session.id, edit(hit.file, hit.mark, next)) + + await expect + .poll( + async () => { + const diff = await sdk.session.diff({ sessionID: session.id }).then((res) => res.data ?? []) + const item = diff.find((item) => item.file === hit.file) + return typeof item?.after === "string" ? item.after : "" + }, + { timeout: 60_000 }, + ) + .toContain(`mark ${next}`) + + await waitMark(page, hit.file, next) + + await expect + .poll(async () => Math.abs((await view.evaluate((el) => el.scrollTop)) - prev), { timeout: 60_000 }) + .toBeLessThanOrEqual(16) + }) + }) +}) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 79c8d42f55..a5c7bf90b3 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -862,6 +862,36 @@ export default function Page() { </div> ) + const reviewEmpty = (input: { loadingClass: string; emptyClass: string }) => { + if (store.changes === "turn") return emptyTurn() + + if (hasReview() && !diffsReady()) { + return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div> + } + + if (reviewEmptyKey() === "session.review.noVcs") { + return ( + <div class={input.emptyClass}> + <div class="flex flex-col gap-3"> + <div class="text-14-medium text-text-strong">Create a Git repository</div> + <div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}> + Track, review, and undo changes in this project + </div> + </div> + <Button size="large" disabled={ui.git} onClick={initGit}> + {ui.git ? "Creating Git repository..." : "Create Git repository"} + </Button> + </div> + ) + } + + return ( + <div class={input.emptyClass}> + <div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div> + </div> + ) + } + const reviewContent = (input: { diffStyle: DiffStyle onDiffStyleChange?: (style: DiffStyle) => void @@ -870,98 +900,25 @@ export default function Page() { emptyClass: string }) => ( <Show when={!store.deferRender}> - <Switch> - <Match when={store.changes === "turn" && !!params.id}> - <SessionReviewTab - title={changesTitle()} - empty={emptyTurn()} - diffs={reviewDiffs} - view={view} - diffStyle={input.diffStyle} - onDiffStyleChange={input.onDiffStyleChange} - onScrollRef={(el) => setTree("reviewScroll", el)} - focusedFile={tree.activeDiff} - onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })} - onLineCommentUpdate={updateCommentInContext} - onLineCommentDelete={removeCommentFromContext} - lineCommentActions={reviewCommentActions()} - comments={comments.all()} - focusedComment={comments.focus()} - onFocusedCommentChange={comments.setFocus} - onViewFile={openReviewFile} - classes={input.classes} - /> - </Match> - <Match when={hasReview()}> - <Show - when={diffsReady()} - fallback={<div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div>} - > - <SessionReviewTab - title={changesTitle()} - diffs={reviewDiffs} - view={view} - diffStyle={input.diffStyle} - onDiffStyleChange={input.onDiffStyleChange} - onScrollRef={(el) => setTree("reviewScroll", el)} - focusedFile={tree.activeDiff} - onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })} - onLineCommentUpdate={updateCommentInContext} - onLineCommentDelete={removeCommentFromContext} - lineCommentActions={reviewCommentActions()} - comments={comments.all()} - focusedComment={comments.focus()} - onFocusedCommentChange={comments.setFocus} - onViewFile={openReviewFile} - classes={input.classes} - /> - </Show> - </Match> - <Match when={true}> - <SessionReviewTab - title={changesTitle()} - empty={ - store.changes === "turn" ? ( - emptyTurn() - ) : reviewEmptyKey() === "session.review.noVcs" ? ( - <div class={input.emptyClass}> - <div class="flex flex-col gap-3"> - <div class="text-14-medium text-text-strong">Create a Git repository</div> - <div - class="text-14-regular text-text-base max-w-md" - style={{ "line-height": "var(--line-height-normal)" }} - > - Track, review, and undo changes in this project - </div> - </div> - <Button size="large" disabled={ui.git} onClick={initGit}> - {ui.git ? "Creating Git repository..." : "Create Git repository"} - </Button> - </div> - ) : ( - <div class={input.emptyClass}> - <div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div> - </div> - ) - } - diffs={reviewDiffs} - view={view} - diffStyle={input.diffStyle} - onDiffStyleChange={input.onDiffStyleChange} - onScrollRef={(el) => setTree("reviewScroll", el)} - focusedFile={tree.activeDiff} - onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })} - onLineCommentUpdate={updateCommentInContext} - onLineCommentDelete={removeCommentFromContext} - lineCommentActions={reviewCommentActions()} - comments={comments.all()} - focusedComment={comments.focus()} - onFocusedCommentChange={comments.setFocus} - onViewFile={openReviewFile} - classes={input.classes} - /> - </Match> - </Switch> + <SessionReviewTab + title={changesTitle()} + empty={reviewEmpty(input)} + diffs={reviewDiffs} + view={view} + diffStyle={input.diffStyle} + onDiffStyleChange={input.onDiffStyleChange} + onScrollRef={(el) => setTree("reviewScroll", el)} + focusedFile={tree.activeDiff} + onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })} + onLineCommentUpdate={updateCommentInContext} + onLineCommentDelete={removeCommentFromContext} + lineCommentActions={reviewCommentActions()} + comments={comments.all()} + focusedComment={comments.focus()} + onFocusedCommentChange={comments.setFocus} + onViewFile={openReviewFile} + classes={input.classes} + /> </Show> ) diff --git a/packages/ui/src/components/file.tsx b/packages/ui/src/components/file.tsx index f42fbb24d2..15915dd52d 100644 --- a/packages/ui/src/components/file.tsx +++ b/packages/ui/src/components/file.tsx @@ -1,10 +1,8 @@ import { sampledChecksum } from "@opencode-ai/util/encode" import { DEFAULT_VIRTUAL_FILE_METRICS, - type ExpansionDirections, type DiffLineAnnotation, type FileContents, - type FileDiffMetadata, File as PierreFile, type FileDiffOptions, FileDiff, @@ -22,7 +20,7 @@ import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMo import { createDefaultOptions, styleVariables } from "../pierre" import { markCommentedDiffLines, markCommentedFileLines } from "../pierre/commented-lines" import { fixDiffSelection, findDiffSide, type DiffSelectionSide } from "../pierre/diff-selection" -import { createFileFind, type FileFindReveal } from "../pierre/file-find" +import { createFileFind } from "../pierre/file-find" import { applyViewerScheme, clearReadyWatcher, @@ -65,21 +63,11 @@ type SharedProps<T> = { search?: FileSearchControl } -export type FileSearchReveal = FileFindReveal - export type FileSearchHandle = { focus: () => void - setQuery: (value: string) => void - clear: () => void - reveal: (hit: FileSearchReveal) => boolean - expand: (hit: FileSearchReveal) => boolean - refresh: () => void } export type FileSearchControl = { - shortcuts?: "global" | "disabled" - showBar?: boolean - disableVirtualization?: boolean register: (handle: FileSearchHandle | null) => void } @@ -121,40 +109,6 @@ const sharedKeys = [ const textKeys = ["file", ...sharedKeys] as const const diffKeys = ["before", "after", ...sharedKeys] as const -function expansionForHit(diff: FileDiffMetadata, hit: FileSearchReveal) { - if (diff.isPartial || diff.hunks.length === 0) return - - const side = - hit.side === "deletions" - ? { - start: (hunk: FileDiffMetadata["hunks"][number]) => hunk.deletionStart, - count: (hunk: FileDiffMetadata["hunks"][number]) => hunk.deletionCount, - } - : { - start: (hunk: FileDiffMetadata["hunks"][number]) => hunk.additionStart, - count: (hunk: FileDiffMetadata["hunks"][number]) => hunk.additionCount, - } - - for (let i = 0; i < diff.hunks.length; i++) { - const hunk = diff.hunks[i] - const start = side.start(hunk) - if (hit.line < start) { - return { - index: i, - direction: i === 0 ? "down" : "both", - } satisfies { index: number; direction: ExpansionDirections } - } - - const end = start + Math.max(side.count(hunk) - 1, -1) - if (hit.line <= end) return - } - - return { - index: diff.hunks.length, - direction: "up", - } satisfies { index: number; direction: ExpansionDirections } -} - // --------------------------------------------------------------------------- // Shared viewer hook // --------------------------------------------------------------------------- @@ -167,7 +121,6 @@ type MouseHit = { type ViewerConfig = { enableLineSelection: () => boolean - search: () => FileSearchControl | undefined selectedLines: () => SelectedLineRange | null | undefined commentedLines: () => SelectedLineRange[] onLineSelectionEnd: (range: SelectedLineRange | null) => void @@ -207,7 +160,6 @@ function useFileViewer(config: ViewerConfig) { wrapper: () => wrapper, overlay: () => overlay, getRoot, - shortcuts: config.search()?.shortcuts, }) // -- selection scheduling -- @@ -407,14 +359,10 @@ function useFileViewer(config: ViewerConfig) { type Viewer = ReturnType<typeof useFileViewer> -type ModeAdapter = Omit< - ViewerConfig, - "enableLineSelection" | "search" | "selectedLines" | "commentedLines" | "onLineSelectionEnd" -> +type ModeAdapter = Omit<ViewerConfig, "enableLineSelection" | "selectedLines" | "commentedLines" | "onLineSelectionEnd"> type ModeConfig = { enableLineSelection: () => boolean - search: () => FileSearchControl | undefined selectedLines: () => SelectedLineRange | null | undefined commentedLines: () => SelectedLineRange[] | undefined onLineSelectionEnd: (range: SelectedLineRange | null) => void @@ -437,7 +385,6 @@ type VirtualStrategy = { function useModeViewer(config: ModeConfig, adapter: ModeAdapter) { return useFileViewer({ enableLineSelection: config.enableLineSelection, - search: config.search, selectedLines: config.selectedLines, commentedLines: () => config.commentedLines() ?? [], onLineSelectionEnd: config.onLineSelectionEnd, @@ -448,32 +395,13 @@ function useModeViewer(config: ModeConfig, adapter: ModeAdapter) { function useSearchHandle(opts: { search: () => FileSearchControl | undefined find: ReturnType<typeof createFileFind> - expand?: (hit: FileSearchReveal) => boolean }) { createEffect(() => { const search = opts.search() if (!search) return const handle = { - focus: () => { - opts.find.focus() - }, - setQuery: (value: string) => { - opts.find.activate() - opts.find.setQuery(value, { scroll: false }) - }, - clear: () => { - opts.find.clear() - }, - reveal: (hit: FileSearchReveal) => { - opts.find.activate() - return opts.find.reveal(hit) - }, - expand: (hit: FileSearchReveal) => opts.expand?.(hit) ?? false, - refresh: () => { - opts.find.activate() - opts.find.refresh() - }, + focus: () => opts.find.focus(), } satisfies FileSearchHandle search.register(handle) @@ -563,6 +491,29 @@ function renderViewer<I extends RenderTarget>(opts: { opts.onReady() } +function preserve(viewer: Viewer) { + const root = scrollParent(viewer.wrapper) + if (!root) return () => {} + + const high = viewer.container.getBoundingClientRect().height + if (!high) return () => {} + + const top = viewer.wrapper.getBoundingClientRect().top - root.getBoundingClientRect().top + const prev = viewer.container.style.minHeight + viewer.container.style.minHeight = `${Math.ceil(high)}px` + + let done = false + return () => { + if (done) return + done = true + viewer.container.style.minHeight = prev + + const next = viewer.wrapper.getBoundingClientRect().top - root.getBoundingClientRect().top + const delta = next - top + if (delta) root.scrollTop += delta + } +} + function scrollParent(el: HTMLElement): HTMLElement | undefined { let parent = el.parentElement while (parent) { @@ -606,7 +557,7 @@ function createLocalVirtualStrategy(host: () => HTMLDivElement | undefined, enab } } -function createSharedVirtualStrategy(host: () => HTMLDivElement | undefined, enabled: () => boolean): VirtualStrategy { +function createSharedVirtualStrategy(host: () => HTMLDivElement | undefined): VirtualStrategy { let shared: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined const release = () => { @@ -616,10 +567,6 @@ function createSharedVirtualStrategy(host: () => HTMLDivElement | undefined, ena return { get: () => { - if (!enabled()) { - release() - return - } if (shared) return shared.virtualizer const container = host() @@ -689,7 +636,6 @@ function diffSelectionSide(node: Node | null) { function ViewerShell(props: { mode: "text" | "diff" viewer: ReturnType<typeof useFileViewer> - search: FileSearchControl | undefined class: string | undefined classList: ComponentProps<"div">["classList"] | undefined }) { @@ -708,7 +654,7 @@ function ViewerShell(props: { onPointerDown={props.viewer.find.onPointerDown} onFocus={props.viewer.find.onFocus} > - <Show when={(props.search?.showBar ?? true) && props.viewer.find.open()}> + <Show when={props.viewer.find.open()}> <FileSearchBar pos={props.viewer.find.pos} query={props.viewer.find.query} @@ -855,7 +801,6 @@ function TextViewer<T>(props: TextFileProps<T>) { viewer = useModeViewer( { enableLineSelection: () => props.enableLineSelection === true, - search: () => local.search, selectedLines: () => local.selectedLines, commentedLines: () => local.commentedLines, onLineSelectionEnd: (range) => local.onLineSelectionEnd?.(range), @@ -941,9 +886,7 @@ function TextViewer<T>(props: TextFileProps<T>) { virtuals.cleanup() }) - return ( - <ViewerShell mode="text" viewer={viewer} search={local.search} class={local.class} classList={local.classList} /> - ) + return <ViewerShell mode="text" viewer={viewer} class={local.class} classList={local.classList} /> } // --------------------------------------------------------------------------- @@ -1029,7 +972,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) { viewer = useModeViewer( { enableLineSelection: () => props.enableLineSelection === true, - search: () => local.search, selectedLines: () => local.selectedLines, commentedLines: () => local.commentedLines, onLineSelectionEnd: (range) => local.onLineSelectionEnd?.(range), @@ -1037,10 +979,7 @@ function DiffViewer<T>(props: DiffFileProps<T>) { adapter, ) - const virtuals = createSharedVirtualStrategy( - () => viewer.container, - () => local.search?.disableVirtualization !== true, - ) + const virtuals = createSharedVirtualStrategy(() => viewer.container) const large = createMemo(() => { const before = typeof local.before?.contents === "string" ? local.before.contents : "" @@ -1074,12 +1013,13 @@ function DiffViewer<T>(props: DiffFileProps<T>) { return { ...perf, disableLineNumbers: true } }) - const notify = () => { + const notify = (done?: VoidFunction) => { notifyRendered({ viewer, isReady: (root) => root.querySelector("[data-line]") != null, settleFrames: 1, onReady: () => { + done?.() setSelectedLines(viewer.lastSelection) viewer.find.refresh({ reset: true }) local.onRendered?.() @@ -1090,20 +1030,6 @@ function DiffViewer<T>(props: DiffFileProps<T>) { useSearchHandle({ search: () => local.search, find: viewer.find, - expand: (hit) => { - const active = instance as - | ((FileDiff<T> | VirtualizedFileDiff<T>) & { - fileDiff?: FileDiffMetadata - }) - | undefined - if (!active?.fileDiff) return false - - const next = expansionForHit(active.fileDiff, hit) - if (!next) return false - - active.expandHunk(next.index, next.direction) - return true - }, }) // -- render instance -- @@ -1114,6 +1040,9 @@ function DiffViewer<T>(props: DiffFileProps<T>) { const virtualizer = virtuals.get() const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : "" const afterContents = typeof local.after?.contents === "string" ? local.after.contents : "" + const done = preserve(viewer) + + onCleanup(done) const cacheKey = (contents: string) => { if (!large()) return sampledChecksum(contents, contents.length) @@ -1138,7 +1067,7 @@ function DiffViewer<T>(props: DiffFileProps<T>) { containerWrapper: viewer.container, }) }, - onReady: notify, + onReady: () => notify(done), }) }) @@ -1158,9 +1087,7 @@ function DiffViewer<T>(props: DiffFileProps<T>) { dragEndSide = undefined }) - return ( - <ViewerShell mode="diff" viewer={viewer} search={local.search} class={local.class} classList={local.classList} /> - ) + return <ViewerShell mode="diff" viewer={viewer} class={local.class} classList={local.classList} /> } // --------------------------------------------------------------------------- diff --git a/packages/ui/src/components/session-review-search.test.ts b/packages/ui/src/components/session-review-search.test.ts deleted file mode 100644 index 060df64071..0000000000 --- a/packages/ui/src/components/session-review-search.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { buildSessionSearchHits, stepSessionSearchIndex } from "./session-review-search" - -describe("session review search", () => { - test("builds hits with line, col, and side", () => { - const hits = buildSessionSearchHits({ - query: "alpha", - files: [ - { - file: "a.txt", - before: "alpha\nbeta alpha", - after: "ALPHA", - }, - ], - }) - - expect(hits).toEqual([ - { file: "a.txt", side: "deletions", line: 1, col: 1, len: 5 }, - { file: "a.txt", side: "deletions", line: 2, col: 6, len: 5 }, - { file: "a.txt", side: "additions", line: 1, col: 1, len: 5 }, - ]) - }) - - test("uses non-overlapping matches", () => { - const hits = buildSessionSearchHits({ - query: "aa", - files: [{ file: "a.txt", after: "aaaa" }], - }) - - expect(hits.map((hit) => hit.col)).toEqual([1, 3]) - }) - - test("wraps next and previous navigation", () => { - expect(stepSessionSearchIndex(5, 0, -1)).toBe(4) - expect(stepSessionSearchIndex(5, 4, 1)).toBe(0) - expect(stepSessionSearchIndex(5, 2, 1)).toBe(3) - expect(stepSessionSearchIndex(0, 0, 1)).toBe(0) - }) -}) diff --git a/packages/ui/src/components/session-review-search.ts b/packages/ui/src/components/session-review-search.ts deleted file mode 100644 index 2cff0adc5a..0000000000 --- a/packages/ui/src/components/session-review-search.ts +++ /dev/null @@ -1,59 +0,0 @@ -export type SessionSearchHit = { - file: string - side: "additions" | "deletions" - line: number - col: number - len: number -} - -type SessionSearchFile = { - file: string - before?: string - after?: string -} - -function hitsForSide(args: { file: string; side: SessionSearchHit["side"]; text: string; needle: string }) { - return args.text.split("\n").flatMap((line, i) => { - if (!line) return [] - - const hay = line.toLowerCase() - let at = hay.indexOf(args.needle) - if (at < 0) return [] - - const out: SessionSearchHit[] = [] - while (at >= 0) { - out.push({ - file: args.file, - side: args.side, - line: i + 1, - col: at + 1, - len: args.needle.length, - }) - at = hay.indexOf(args.needle, at + args.needle.length) - } - - return out - }) -} - -export function buildSessionSearchHits(args: { query: string; files: SessionSearchFile[] }) { - const value = args.query.trim().toLowerCase() - if (!value) return [] - - return args.files.flatMap((file) => { - const out: SessionSearchHit[] = [] - if (typeof file.before === "string") { - out.push(...hitsForSide({ file: file.file, side: "deletions", text: file.before, needle: value })) - } - if (typeof file.after === "string") { - out.push(...hitsForSide({ file: file.file, side: "additions", text: file.after, needle: value })) - } - return out - }) -} - -export function stepSessionSearchIndex(total: number, current: number, dir: 1 | -1) { - if (total <= 0) return 0 - if (current < 0 || current >= total) return dir > 0 ? 0 : total - 1 - return (current + dir + total) % total -} diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 62c70e8647..49c561e0bd 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -9,9 +9,6 @@ import { IconButton } from "./icon-button" import { StickyAccordionHeader } from "./sticky-accordion-header" import { Tooltip } from "./tooltip" import { ScrollView } from "./scroll-view" -import { FileSearchBar } from "./file-search" -import type { FileSearchHandle } from "./file" -import { buildSessionSearchHits, stepSessionSearchIndex, type SessionSearchHit } from "./session-review-search" import { useFileComponent } from "../context/file" import { useI18n } from "../context/i18n" import { getDirectory, getFilename } from "@opencode-ai/util/path" @@ -63,6 +60,8 @@ export type SessionReviewCommentActions = { export type SessionReviewFocus = { file: string; id: string } +type ReviewDiff = FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> } + export interface SessionReviewProps { title?: JSX.Element empty?: JSX.Element @@ -86,7 +85,7 @@ export interface SessionReviewProps { classList?: Record<string, boolean | undefined> classes?: { root?: string; header?: string; container?: string } actions?: JSX.Element - diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[] + diffs: ReviewDiff[] onViewFile?: (file: string) => void readFile?: (path: string) => Promise<FileContent | undefined> } @@ -135,15 +134,10 @@ type SessionReviewSelection = { export const SessionReview = (props: SessionReviewProps) => { let scroll: HTMLDivElement | undefined - let searchInput: HTMLInputElement | undefined let focusToken = 0 - let revealToken = 0 - let highlightedFile: string | undefined const i18n = useI18n() const fileComponent = useFileComponent() const anchors = new Map<string, HTMLElement>() - const searchHandles = new Map<string, FileSearchHandle>() - const readyFiles = new Set<string>() const [store, setStore] = createStore<{ open: string[]; force: Record<string, boolean> }>({ open: [], force: {}, @@ -152,18 +146,12 @@ export const SessionReview = (props: SessionReviewProps) => { const [selection, setSelection] = createSignal<SessionReviewSelection | null>(null) const [commenting, setCommenting] = createSignal<SessionReviewSelection | null>(null) const [opened, setOpened] = createSignal<SessionReviewFocus | null>(null) - const [searchOpen, setSearchOpen] = createSignal(false) - const [searchQuery, setSearchQuery] = createSignal("") - const [searchActive, setSearchActive] = createSignal(0) - const [searchPos, setSearchPos] = createSignal({ top: 8, right: 8 }) const open = () => props.open ?? store.open - const files = createMemo(() => props.diffs.map((d) => d.file)) - const diffs = createMemo(() => new Map(props.diffs.map((d) => [d.file, d] as const))) + const files = createMemo(() => props.diffs.map((diff) => diff.file)) + const diffs = createMemo(() => new Map(props.diffs.map((diff) => [diff.file, diff] as const))) const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified") const hasDiffs = () => files().length > 0 - const searchValue = createMemo(() => searchQuery().trim()) - const searchExpanded = createMemo(() => searchValue().length > 0) const handleChange = (open: string[]) => { props.onOpenChange?.(open) @@ -176,266 +164,8 @@ export const SessionReview = (props: SessionReviewProps) => { handleChange(next) } - const clearViewerSearch = () => { - for (const handle of searchHandles.values()) handle.clear() - highlightedFile = undefined - } - const openFileLabel = () => i18n.t("ui.sessionReview.openFile") - const selectionLabel = (range: SelectedLineRange) => { - const start = Math.min(range.start, range.end) - const end = Math.max(range.start, range.end) - if (start === end) return i18n.t("ui.sessionReview.selection.line", { line: start }) - return i18n.t("ui.sessionReview.selection.lines", { start, end }) - } - - const focusSearch = () => { - if (!hasDiffs()) return - setSearchOpen(true) - requestAnimationFrame(() => { - searchInput?.focus() - searchInput?.select() - }) - } - - const closeSearch = () => { - revealToken++ - setSearchOpen(false) - setSearchQuery("") - setSearchActive(0) - clearViewerSearch() - } - - const positionSearchBar = () => { - if (typeof window === "undefined") return - if (!scroll) return - - const rect = scroll.getBoundingClientRect() - const title = parseFloat(getComputedStyle(scroll).getPropertyValue("--session-title-height")) - const header = Number.isNaN(title) ? 0 : title - setSearchPos({ - top: Math.round(rect.top) + header - 4, - right: Math.round(window.innerWidth - rect.right) + 8, - }) - } - - const searchHits = createMemo(() => - buildSessionSearchHits({ - query: searchQuery(), - files: props.diffs.flatMap((diff) => { - if (mediaKindFromPath(diff.file)) return [] - - return [ - { - file: diff.file, - before: typeof diff.before === "string" ? diff.before : undefined, - after: typeof diff.after === "string" ? diff.after : undefined, - }, - ] - }), - }), - ) - - const waitForViewer = (file: string, token: number) => - new Promise<FileSearchHandle | undefined>((resolve) => { - let attempt = 0 - - const tick = () => { - if (token !== revealToken) { - resolve(undefined) - return - } - - const handle = searchHandles.get(file) - if (handle && readyFiles.has(file)) { - resolve(handle) - return - } - - if (attempt >= 180) { - resolve(undefined) - return - } - - attempt++ - requestAnimationFrame(tick) - } - - tick() - }) - - const waitForFrames = (count: number, token: number) => - new Promise<boolean>((resolve) => { - const tick = (left: number) => { - if (token !== revealToken) { - resolve(false) - return - } - - if (left <= 0) { - resolve(true) - return - } - - requestAnimationFrame(() => tick(left - 1)) - } - - tick(count) - }) - - const revealSearchHit = async (token: number, hit: SessionSearchHit, query: string) => { - const diff = diffs().get(hit.file) - if (!diff) return - - if (!open().includes(hit.file)) { - handleChange([...open(), hit.file]) - } - - if (!mediaKindFromPath(hit.file) && diff.additions + diff.deletions > MAX_DIFF_CHANGED_LINES) { - setStore("force", hit.file, true) - } - - const handle = await waitForViewer(hit.file, token) - if (!handle || token !== revealToken) return - if (searchValue() !== query) return - if (!(await waitForFrames(2, token))) return - - if (highlightedFile && highlightedFile !== hit.file) { - searchHandles.get(highlightedFile)?.clear() - highlightedFile = undefined - } - - anchors.get(hit.file)?.scrollIntoView({ block: "nearest" }) - - let done = false - for (let i = 0; i < 4; i++) { - if (token !== revealToken) return - if (searchValue() !== query) return - - handle.setQuery(query) - if (handle.reveal(hit)) { - done = true - break - } - - const expanded = handle.expand(hit) - handle.refresh() - if (!(await waitForFrames(expanded ? 2 : 1, token))) return - } - - if (!done) return - - if (!(await waitForFrames(1, token))) return - handle.reveal(hit) - - highlightedFile = hit.file - } - - const navigateSearch = (dir: 1 | -1) => { - const total = searchHits().length - if (total <= 0) return - setSearchActive((value) => stepSessionSearchIndex(total, value, dir)) - } - - const inReview = (node: unknown, path?: unknown[]) => { - if (node === searchInput) return true - if (path?.some((item) => item === scroll || item === searchInput)) return true - if (path?.some((item) => item instanceof HTMLElement && item.dataset.component === "session-review")) { - return true - } - if (!(node instanceof Node)) return false - if (searchInput?.contains(node)) return true - if (node instanceof HTMLElement && node.closest("[data-component='session-review']")) return true - if (!scroll) return false - return scroll.contains(node) - } - - createEffect(() => { - if (typeof window === "undefined") return - - const onKeyDown = (event: KeyboardEvent) => { - const mod = event.metaKey || event.ctrlKey - if (!mod) return - - const key = event.key.toLowerCase() - if (key !== "f" && key !== "g") return - - if (key === "f") { - if (!hasDiffs()) return - event.preventDefault() - event.stopPropagation() - focusSearch() - return - } - - const path = typeof event.composedPath === "function" ? event.composedPath() : undefined - if (!inReview(event.target, path) && !inReview(document.activeElement, path)) return - if (!searchOpen()) return - event.preventDefault() - event.stopPropagation() - navigateSearch(event.shiftKey ? -1 : 1) - } - - window.addEventListener("keydown", onKeyDown, { capture: true }) - onCleanup(() => window.removeEventListener("keydown", onKeyDown, { capture: true })) - }) - - createEffect(() => { - diffStyle() - searchExpanded() - readyFiles.clear() - }) - - createEffect(() => { - if (!searchOpen()) return - if (!scroll) return - - const root = scroll - - requestAnimationFrame(positionSearchBar) - window.addEventListener("resize", positionSearchBar, { passive: true }) - const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(positionSearchBar) - observer?.observe(root) - - onCleanup(() => { - window.removeEventListener("resize", positionSearchBar) - observer?.disconnect() - }) - }) - - createEffect(() => { - const total = searchHits().length - if (total === 0) { - if (searchActive() !== 0) setSearchActive(0) - return - } - - if (searchActive() >= total) setSearchActive(total - 1) - }) - - createEffect(() => { - diffStyle() - const query = searchValue() - const hits = searchHits() - const token = ++revealToken - if (!query || hits.length === 0) { - clearViewerSearch() - return - } - - const hit = hits[Math.min(searchActive(), hits.length - 1)] - if (!hit) return - void revealSearchHit(token, hit, query) - }) - - onCleanup(() => { - revealToken++ - clearViewerSearch() - readyFiles.clear() - searchHandles.clear() - }) - const selectionSide = (range: SelectedLineRange) => range.endSide ?? range.side ?? "additions" const selectionPreview = (diff: FileDiff, range: SelectedLineRange) => { @@ -499,58 +229,6 @@ export const SessionReview = (props: SessionReviewProps) => { }) }) - const handleReviewKeyDown = (event: KeyboardEvent) => { - if (event.defaultPrevented) return - - const mod = event.metaKey || event.ctrlKey - const key = event.key.toLowerCase() - const target = event.target - if (mod && key === "f") { - event.preventDefault() - event.stopPropagation() - focusSearch() - return - } - - if (mod && key === "g") { - if (!searchOpen()) return - event.preventDefault() - event.stopPropagation() - navigateSearch(event.shiftKey ? -1 : 1) - } - } - - const handleSearchInputKeyDown = (event: KeyboardEvent) => { - const mod = event.metaKey || event.ctrlKey - const key = event.key.toLowerCase() - - if (mod && key === "g") { - event.preventDefault() - event.stopPropagation() - navigateSearch(event.shiftKey ? -1 : 1) - return - } - - if (mod && key === "f") { - event.preventDefault() - event.stopPropagation() - focusSearch() - return - } - - if (event.key === "Escape") { - event.preventDefault() - event.stopPropagation() - closeSearch() - return - } - - if (event.key !== "Enter") return - event.preventDefault() - event.stopPropagation() - navigateSearch(event.shiftKey ? -1 : 1) - } - return ( <div data-component="session-review" class={props.class} classList={props.classList}> <div data-slot="session-review-header" class={props.classes?.header}> @@ -594,31 +272,10 @@ export const SessionReview = (props: SessionReviewProps) => { props.scrollRef?.(el) }} onScroll={props.onScroll as any} - onKeyDown={handleReviewKeyDown} classList={{ [props.classes?.root ?? ""]: !!props.classes?.root, }} > - <Show when={searchOpen()}> - <FileSearchBar - pos={searchPos} - query={searchQuery} - index={() => (searchHits().length ? Math.min(searchActive(), searchHits().length - 1) : 0)} - count={() => searchHits().length} - setInput={(el) => { - searchInput = el - }} - onInput={(value) => { - setSearchQuery(value) - setSearchActive(0) - }} - onKeyDown={(event) => handleSearchInputKeyDown(event)} - onClose={closeSearch} - onPrev={() => navigateSearch(-1)} - onNext={() => navigateSearch(1)} - /> - </Show> - <div data-slot="session-review-container" class={props.classes?.container}> <Show when={hasDiffs()} fallback={props.empty}> <div class="pb-6"> @@ -627,8 +284,7 @@ export const SessionReview = (props: SessionReviewProps) => { {(file) => { let wrapper: HTMLDivElement | undefined - const diff = createMemo(() => diffs().get(file)) - const item = () => diff()! + const item = createMemo(() => diffs().get(file)!) const expanded = createMemo(() => open().includes(file)) const force = () => !!store.force[file] @@ -720,9 +376,6 @@ export const SessionReview = (props: SessionReviewProps) => { onCleanup(() => { anchors.delete(file) - readyFiles.delete(file) - searchHandles.delete(file) - if (highlightedFile === file) highlightedFile = undefined }) const handleLineSelected = (range: SelectedLineRange | null) => { @@ -839,9 +492,7 @@ export const SessionReview = (props: SessionReviewProps) => { mode="diff" preloadedDiff={item().preloaded} diffStyle={diffStyle()} - expansionLineCount={searchExpanded() ? Number.MAX_SAFE_INTEGER : 20} onRendered={() => { - readyFiles.add(file) props.onDiffRendered?.() }} enableLineSelection={props.onLineComment != null} @@ -854,21 +505,6 @@ export const SessionReview = (props: SessionReviewProps) => { renderHoverUtility={props.onLineComment ? commentsUi.renderHoverUtility : undefined} selectedLines={selectedLines()} commentedLines={commentedLines()} - search={{ - shortcuts: "disabled", - showBar: false, - disableVirtualization: searchExpanded(), - register: (handle: FileSearchHandle | null) => { - if (!handle) { - searchHandles.delete(file) - readyFiles.delete(file) - if (highlightedFile === file) highlightedFile = undefined - return - } - - searchHandles.set(file, handle) - }, - }} before={{ name: file, contents: typeof item().before === "string" ? item().before : "", diff --git a/packages/ui/src/pierre/file-find.ts b/packages/ui/src/pierre/file-find.ts index 7d55cfa72b..ee608152d5 100644 --- a/packages/ui/src/pierre/file-find.ts +++ b/packages/ui/src/pierre/file-find.ts @@ -8,20 +8,6 @@ export type FindHost = { isOpen: () => boolean } -type FileFindSide = "additions" | "deletions" - -export type FileFindReveal = { - side: FileFindSide - line: number - col: number - len: number -} - -type FileFindHit = FileFindReveal & { - range: Range - alt?: number -} - const hosts = new Set<FindHost>() let target: FindHost | undefined let current: FindHost | undefined @@ -112,7 +98,6 @@ type CreateFileFindOptions = { wrapper: () => HTMLElement | undefined overlay: () => HTMLDivElement | undefined getRoot: () => ShadowRoot | undefined - shortcuts?: "global" | "disabled" } export function createFileFind(opts: CreateFileFindOptions) { @@ -120,7 +105,7 @@ export function createFileFind(opts: CreateFileFindOptions) { let overlayFrame: number | undefined let overlayScroll: HTMLElement[] = [] let mode: "highlights" | "overlay" = "overlay" - let hits: FileFindHit[] = [] + let hits: Range[] = [] const [open, setOpen] = createSignal(false) const [query, setQuery] = createSignal("") @@ -161,7 +146,7 @@ export function createFileFind(opts: CreateFileFindOptions) { const frag = document.createDocumentFragment() for (let i = 0; i < hits.length; i++) { - const range = hits[i].range + const range = hits[i] const active = i === currentIndex for (const rect of Array.from(range.getClientRects())) { if (!rect.width || !rect.height) continue @@ -237,7 +222,7 @@ export function createFileFind(opts: CreateFileFindOptions) { const scan = (root: ShadowRoot, value: string) => { const needle = value.toLowerCase() - const ranges: FileFindHit[] = [] + const ranges: Range[] = [] const cols = Array.from(root.querySelectorAll("[data-content] [data-line], [data-column-content]")).filter( (node): node is HTMLElement => node instanceof HTMLElement, ) @@ -250,28 +235,6 @@ export function createFileFind(opts: CreateFileFindOptions) { let at = hay.indexOf(needle) if (at === -1) continue - const row = col.closest("[data-line], [data-alt-line]") - if (!(row instanceof HTMLElement)) continue - - const primary = parseInt(row.dataset.line ?? "", 10) - const alt = parseInt(row.dataset.altLine ?? "", 10) - const line = (() => { - if (!Number.isNaN(primary)) return primary - if (!Number.isNaN(alt)) return alt - })() - if (line === undefined) continue - - const side = (() => { - const code = col.closest("[data-code]") - if (code instanceof HTMLElement) return code.hasAttribute("data-deletions") ? "deletions" : "additions" - - const row = col.closest("[data-line-type]") - if (!(row instanceof HTMLElement)) return "additions" - const type = row.dataset.lineType - if (type === "change-deletion") return "deletions" - return "additions" - })() as FileFindSide - const nodes: Text[] = [] const ends: number[] = [] const walker = document.createTreeWalker(col, NodeFilter.SHOW_TEXT) @@ -305,14 +268,7 @@ export function createFileFind(opts: CreateFileFindOptions) { const range = document.createRange() range.setStart(start.node, start.offset) range.setEnd(end.node, end.offset) - ranges.push({ - range, - side, - line, - alt: Number.isNaN(alt) ? undefined : alt, - col: at + 1, - len: value.length, - }) + ranges.push(range) at = hay.indexOf(needle, at + value.length) } } @@ -321,17 +277,12 @@ export function createFileFind(opts: CreateFileFindOptions) { } const scrollToRange = (range: Range) => { - const scroll = () => { - const start = range.startContainer - const el = start instanceof Element ? start : start.parentElement - el?.scrollIntoView({ block: "center", inline: "center" }) - } - - scroll() - requestAnimationFrame(scroll) + const start = range.startContainer + const el = start instanceof Element ? start : start.parentElement + el?.scrollIntoView({ block: "center", inline: "center" }) } - const setHighlights = (ranges: FileFindHit[], currentIndex: number) => { + const setHighlights = (ranges: Range[], currentIndex: number) => { const api = (globalThis as unknown as { CSS?: { highlights?: any }; Highlight?: any }).CSS?.highlights const Highlight = (globalThis as unknown as { Highlight?: any }).Highlight if (!api || typeof Highlight !== "function") return false @@ -339,37 +290,14 @@ export function createFileFind(opts: CreateFileFindOptions) { api.delete("opencode-find") api.delete("opencode-find-current") - const active = ranges[currentIndex]?.range + const active = ranges[currentIndex] if (active) api.set("opencode-find-current", new Highlight(active)) - const rest = ranges.flatMap((hit, i) => (i === currentIndex ? [] : [hit.range])) + const rest = ranges.filter((_, i) => i !== currentIndex) if (rest.length > 0) api.set("opencode-find", new Highlight(...rest)) return true } - const select = (currentIndex: number, scroll: boolean) => { - const active = hits[currentIndex]?.range - if (!active) return false - - setIndex(currentIndex) - - if (mode === "highlights") { - if (!setHighlights(hits, currentIndex)) { - mode = "overlay" - apply({ reset: true, scroll }) - return false - } - if (scroll) scrollToRange(active) - return true - } - - clearHighlightFind() - syncOverlayScroll() - if (scroll) scrollToRange(active) - scheduleOverlay() - return true - } - const apply = (args?: { reset?: boolean; scroll?: boolean }) => { if (!open()) return @@ -393,7 +321,7 @@ export function createFileFind(opts: CreateFileFindOptions) { setCount(total) setIndex(currentIndex) - const active = ranges[currentIndex]?.range + const active = ranges[currentIndex] if (mode === "highlights") { clearOverlay() clearOverlayScroll() @@ -420,23 +348,11 @@ export function createFileFind(opts: CreateFileFindOptions) { if (current === host) current = undefined } - const clear = () => { - setQuery("") - clearFind() - } - - const activate = () => { - if (opts.shortcuts !== "disabled") { - if (current && current !== host) current.close() - current = host - target = host - } - - if (!open()) setOpen(true) - } - const focus = () => { - activate() + if (current && current !== host) current.close() + current = host + target = host + if (!open()) setOpen(true) requestAnimationFrame(() => { apply({ scroll: true }) input?.focus() @@ -450,30 +366,25 @@ export function createFileFind(opts: CreateFileFindOptions) { if (total <= 0) return const currentIndex = (index() + dir + total) % total - select(currentIndex, true) - } + setIndex(currentIndex) - const reveal = (targetHit: FileFindReveal) => { - if (!open()) return false - if (hits.length === 0) return false + const active = hits[currentIndex] + if (!active) return - const exact = hits.findIndex( - (hit) => - hit.side === targetHit.side && - hit.line === targetHit.line && - hit.col === targetHit.col && - hit.len === targetHit.len, - ) - const fallback = hits.findIndex( - (hit) => - (hit.line === targetHit.line || hit.alt === targetHit.line) && - hit.col === targetHit.col && - hit.len === targetHit.len, - ) + if (mode === "highlights") { + if (!setHighlights(hits, currentIndex)) { + mode = "overlay" + apply({ reset: true, scroll: true }) + return + } + scrollToRange(active) + return + } - const nextIndex = exact >= 0 ? exact : fallback - if (nextIndex < 0) return false - return select(nextIndex, true) + clearHighlightFind() + syncOverlayScroll() + scrollToRange(active) + scheduleOverlay() } const host: FindHost = { @@ -486,21 +397,17 @@ export function createFileFind(opts: CreateFileFindOptions) { onMount(() => { mode = supportsHighlights() ? "highlights" : "overlay" - if (opts.shortcuts !== "disabled") { - installShortcuts() - hosts.add(host) - if (!target) target = host - } + installShortcuts() + hosts.add(host) + if (!target) target = host onCleanup(() => { - if (opts.shortcuts !== "disabled") { - hosts.delete(host) - if (current === host) { - current = undefined - clearHighlightFind() - } - if (target === host) target = undefined + hosts.delete(host) + if (current === host) { + current = undefined + clearHighlightFind() } + if (target === host) target = undefined }) }) @@ -541,25 +448,20 @@ export function createFileFind(opts: CreateFileFindOptions) { setInput: (el: HTMLInputElement) => { input = el }, - setQuery: (value: string, args?: { scroll?: boolean }) => { + setQuery: (value: string) => { setQuery(value) setIndex(0) - apply({ reset: true, scroll: args?.scroll ?? true }) + apply({ reset: true, scroll: true }) }, - clear, - activate, focus, close, next, - reveal, refresh: (args?: { reset?: boolean; scroll?: boolean }) => apply(args), onPointerDown: () => { - if (opts.shortcuts === "disabled") return target = host opts.wrapper()?.focus({ preventScroll: true }) }, onFocus: () => { - if (opts.shortcuts === "disabled") return target = host }, onInputKeyDown: (event: KeyboardEvent) => { From 2aae0d3493ac51aa2fd3929c6db0814ab795b04b Mon Sep 17 00:00:00 2001 From: Johannes Loher <johannes.loher@fg4f.de> Date: Wed, 11 Mar 2026 19:10:45 +0100 Subject: [PATCH 082/200] fix(core): read stdout and stderr in PackageRegistry.info before waiting for the process to exit (#16998) --- packages/opencode/src/bun/registry.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/bun/registry.ts b/packages/opencode/src/bun/registry.ts index 1fc8531442..e43e20e6c5 100644 --- a/packages/opencode/src/bun/registry.ts +++ b/packages/opencode/src/bun/registry.ts @@ -1,5 +1,4 @@ import semver from "semver" -import { text } from "node:stream/consumers" import { Log } from "../util/log" import { Process } from "../util/process" @@ -11,26 +10,21 @@ export namespace PackageRegistry { } export async function info(pkg: string, field: string, cwd?: string): Promise<string | null> { - const result = Process.spawn([which(), "info", pkg, field], { + const { code, stdout, stderr } = await Process.run([which(), "info", pkg, field], { cwd, - stdout: "pipe", - stderr: "pipe", env: { ...process.env, BUN_BE_BUN: "1", }, + nothrow: true, }) - const code = await result.exited - const stdout = result.stdout ? await text(result.stdout) : "" - const stderr = result.stderr ? await text(result.stderr) : "" - if (code !== 0) { - log.warn("bun info failed", { pkg, field, code, stderr }) + log.warn("bun info failed", { pkg, field, code, stderr: stderr.toString() }) return null } - const value = stdout.trim() + const value = stdout.toString().trim() if (!value) return null return value } From 981c7b9e375b7d9ac57d2d6a3179451139b2b99b Mon Sep 17 00:00:00 2001 From: Kit Langton <kit.langton@gmail.com> Date: Wed, 11 Mar 2026 14:18:58 -0400 Subject: [PATCH 083/200] refactor(account): tighten effect-based account flows (#17072) --- packages/opencode/AGENTS.md | 34 ++ packages/opencode/src/account/account.sql.ts | 18 +- packages/opencode/src/account/index.ts | 8 +- packages/opencode/src/account/repo.ts | 190 +++++----- packages/opencode/src/account/schema.ts | 26 +- packages/opencode/src/account/service.ts | 355 ++++++++---------- packages/opencode/src/cli/cmd/account.ts | 8 +- packages/opencode/test/account/repo.test.ts | 70 ++-- .../opencode/test/account/service.test.ts | 35 +- 9 files changed, 390 insertions(+), 354 deletions(-) diff --git a/packages/opencode/AGENTS.md b/packages/opencode/AGENTS.md index dcfc336d65..930297baa9 100644 --- a/packages/opencode/AGENTS.md +++ b/packages/opencode/AGENTS.md @@ -8,3 +8,37 @@ - **Command**: `bun run db generate --name <slug>`. - **Output**: creates `migration/<timestamp>_<slug>/migration.sql` and `snapshot.json`. - **Tests**: migration tests should read the per-folder layout (no `_journal.json`). + +# opencode Effect guide + +Instructions to follow when writing Effect. + +## Schemas + +- Use `Schema.Class` for data types with multiple fields. +- Use branded schemas (`Schema.brand`) for single-value types. + +## Services + +- Services use `ServiceMap.Service<ServiceName, ServiceName.Service>()("@console/<Name>")`. +- In `Layer.effect`, always return service implementations with `ServiceName.of({ ... })`, never a plain object. + +## Errors + +- Use `Schema.TaggedErrorClass` for typed errors. +- For defect-like causes, use `Schema.Defect` instead of `unknown`. +- In `Effect.gen`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches. + +## Effects + +- Use `Effect.gen(function* () { ... })` for composition. +- Use `Effect.fn("ServiceName.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers. +- `Effect.fn` / `Effect.fnUntraced` accept pipeable operators as extra arguments, so avoid unnecessary `flow` or outer `.pipe()` wrappers. + +## Time + +- Prefer `DateTime.nowAsDate` over `new Date(yield* Clock.currentTimeMillis)` when you need a `Date`. + +## Errors + +- In `Effect.gen/fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches. diff --git a/packages/opencode/src/account/account.sql.ts b/packages/opencode/src/account/account.sql.ts index e66b3c2992..35bfd1e3ed 100644 --- a/packages/opencode/src/account/account.sql.ts +++ b/packages/opencode/src/account/account.sql.ts @@ -1,20 +1,24 @@ import { sqliteTable, text, integer, primaryKey } from "drizzle-orm/sqlite-core" + +import { type AccessToken, type AccountID, type OrgID, type RefreshToken } from "./schema" import { Timestamps } from "../storage/schema.sql" export const AccountTable = sqliteTable("account", { - id: text().primaryKey(), + id: text().$type<AccountID>().primaryKey(), email: text().notNull(), url: text().notNull(), - access_token: text().notNull(), - refresh_token: text().notNull(), + access_token: text().$type<AccessToken>().notNull(), + refresh_token: text().$type<RefreshToken>().notNull(), token_expiry: integer(), ...Timestamps, }) export const AccountStateTable = sqliteTable("account_state", { id: integer().primaryKey(), - active_account_id: text().references(() => AccountTable.id, { onDelete: "set null" }), - active_org_id: text(), + active_account_id: text() + .$type<AccountID>() + .references(() => AccountTable.id, { onDelete: "set null" }), + active_org_id: text().$type<OrgID>(), }) // LEGACY @@ -23,8 +27,8 @@ export const ControlAccountTable = sqliteTable( { email: text().notNull(), url: text().notNull(), - access_token: text().notNull(), - refresh_token: text().notNull(), + access_token: text().$type<AccessToken>().notNull(), + refresh_token: text().$type<RefreshToken>().notNull(), token_expiry: integer(), active: integer({ mode: "boolean" }) .notNull() diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts index b48ada1fb5..ed4c3d8798 100644 --- a/packages/opencode/src/account/index.ts +++ b/packages/opencode/src/account/index.ts @@ -1,4 +1,4 @@ -import { Effect, Option, ServiceMap } from "effect" +import { Effect, Option } from "effect" import { Account as AccountSchema, @@ -13,13 +13,11 @@ export { AccessToken, AccountID, OrgID } from "./service" import { runtime } from "@/effect/runtime" -type AccountServiceShape = ServiceMap.Service.Shape<typeof AccountService> - -function runSync<A>(f: (service: AccountServiceShape) => Effect.Effect<A, AccountError>) { +function runSync<A>(f: (service: AccountService.Service) => Effect.Effect<A, AccountError>) { return runtime.runSync(AccountService.use(f)) } -function runPromise<A>(f: (service: AccountServiceShape) => Effect.Effect<A, AccountError>) { +function runPromise<A>(f: (service: AccountService.Service) => Effect.Effect<A, AccountError>) { return runtime.runPromise(AccountService.use(f)) } diff --git a/packages/opencode/src/account/repo.ts b/packages/opencode/src/account/repo.ts index 65f56727b9..5caf1a3b94 100644 --- a/packages/opencode/src/account/repo.ts +++ b/packages/opencode/src/account/repo.ts @@ -3,43 +3,16 @@ import { Effect, Layer, Option, Schema, ServiceMap } from "effect" import { Database } from "@/storage/db" import { AccountStateTable, AccountTable } from "./account.sql" -import { Account, AccountID, AccountRepoError, OrgID } from "./schema" +import { AccessToken, Account, AccountID, AccountRepoError, OrgID, RefreshToken } from "./schema" export type AccountRow = (typeof AccountTable)["$inferSelect"] -const decodeAccount = Schema.decodeUnknownSync(Account) - type DbClient = Parameters<typeof Database.use>[0] extends (db: infer T) => unknown ? T : never const ACCOUNT_STATE_ID = 1 -const db = <A>(run: (db: DbClient) => A) => - Effect.try({ - try: () => Database.use(run), - catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }), - }) - -const current = (db: DbClient) => { - const state = db.select().from(AccountStateTable).where(eq(AccountStateTable.id, ACCOUNT_STATE_ID)).get() - if (!state?.active_account_id) return - const account = db.select().from(AccountTable).where(eq(AccountTable.id, state.active_account_id)).get() - if (!account) return - return { ...account, active_org_id: state.active_org_id ?? null } -} - -const setState = (db: DbClient, accountID: AccountID, orgID: string | null) => - db - .insert(AccountStateTable) - .values({ id: ACCOUNT_STATE_ID, active_account_id: accountID, active_org_id: orgID }) - .onConflictDoUpdate({ - target: AccountStateTable.id, - set: { active_account_id: accountID, active_org_id: orgID }, - }) - .run() - -export class AccountRepo extends ServiceMap.Service< - AccountRepo, - { +export namespace AccountRepo { + export interface Service { readonly active: () => Effect.Effect<Option.Option<Account>, AccountRepoError> readonly list: () => Effect.Effect<Account[], AccountRepoError> readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountRepoError> @@ -47,62 +20,96 @@ export class AccountRepo extends ServiceMap.Service< readonly getRow: (accountID: AccountID) => Effect.Effect<Option.Option<AccountRow>, AccountRepoError> readonly persistToken: (input: { accountID: AccountID - accessToken: string - refreshToken: string + accessToken: AccessToken + refreshToken: RefreshToken expiry: Option.Option<number> }) => Effect.Effect<void, AccountRepoError> readonly persistAccount: (input: { id: AccountID email: string url: string - accessToken: string - refreshToken: string + accessToken: AccessToken + refreshToken: RefreshToken expiry: number orgID: Option.Option<OrgID> }) => Effect.Effect<void, AccountRepoError> } ->()("@opencode/AccountRepo") { - static readonly layer: Layer.Layer<AccountRepo> = Layer.succeed( - AccountRepo, - AccountRepo.of({ - active: Effect.fn("AccountRepo.active")(() => - db((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decodeAccount(row)) : Option.none()))), - ), +} - list: Effect.fn("AccountRepo.list")(() => - db((db) => +export class AccountRepo extends ServiceMap.Service<AccountRepo, AccountRepo.Service>()("@opencode/AccountRepo") { + static readonly layer: Layer.Layer<AccountRepo> = Layer.effect( + AccountRepo, + Effect.gen(function* () { + const decode = Schema.decodeUnknownSync(Account) + + const query = <A>(f: (db: DbClient) => A) => + Effect.try({ + try: () => Database.use(f), + catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }), + }) + + const tx = <A>(f: (db: DbClient) => A) => + Effect.try({ + try: () => Database.transaction(f), + catch: (cause) => new AccountRepoError({ message: "Database operation failed", cause }), + }) + + const current = (db: DbClient) => { + const state = db.select().from(AccountStateTable).where(eq(AccountStateTable.id, ACCOUNT_STATE_ID)).get() + if (!state?.active_account_id) return + const account = db.select().from(AccountTable).where(eq(AccountTable.id, state.active_account_id)).get() + if (!account) return + return { ...account, active_org_id: state.active_org_id ?? null } + } + + const state = (db: DbClient, accountID: AccountID, orgID: Option.Option<OrgID>) => { + const id = Option.getOrNull(orgID) + return db + .insert(AccountStateTable) + .values({ id: ACCOUNT_STATE_ID, active_account_id: accountID, active_org_id: id }) + .onConflictDoUpdate({ + target: AccountStateTable.id, + set: { active_account_id: accountID, active_org_id: id }, + }) + .run() + } + + const active = Effect.fn("AccountRepo.active")(() => + query((db) => current(db)).pipe(Effect.map((row) => (row ? Option.some(decode(row)) : Option.none()))), + ) + + const list = Effect.fn("AccountRepo.list")(() => + query((db) => db .select() .from(AccountTable) .all() - .map((row) => decodeAccount({ ...row, active_org_id: null })), + .map((row: AccountRow) => decode({ ...row, active_org_id: null })), ), - ), + ) - remove: Effect.fn("AccountRepo.remove")((accountID: AccountID) => - db((db) => - Database.transaction((tx) => { - tx.update(AccountStateTable) - .set({ active_account_id: null, active_org_id: null }) - .where(eq(AccountStateTable.active_account_id, accountID)) - .run() - tx.delete(AccountTable).where(eq(AccountTable.id, accountID)).run() - }), - ).pipe(Effect.asVoid), - ), + const remove = Effect.fn("AccountRepo.remove")((accountID: AccountID) => + tx((db) => { + db.update(AccountStateTable) + .set({ active_account_id: null, active_org_id: null }) + .where(eq(AccountStateTable.active_account_id, accountID)) + .run() + db.delete(AccountTable).where(eq(AccountTable.id, accountID)).run() + }).pipe(Effect.asVoid), + ) - use: Effect.fn("AccountRepo.use")((accountID: AccountID, orgID: Option.Option<OrgID>) => - db((db) => setState(db, accountID, Option.getOrNull(orgID))).pipe(Effect.asVoid), - ), + const use = Effect.fn("AccountRepo.use")((accountID: AccountID, orgID: Option.Option<OrgID>) => + query((db) => state(db, accountID, orgID)).pipe(Effect.asVoid), + ) - getRow: Effect.fn("AccountRepo.getRow")((accountID: AccountID) => - db((db) => db.select().from(AccountTable).where(eq(AccountTable.id, accountID)).get()).pipe( + const getRow = Effect.fn("AccountRepo.getRow")((accountID: AccountID) => + query((db) => db.select().from(AccountTable).where(eq(AccountTable.id, accountID)).get()).pipe( Effect.map(Option.fromNullishOr), ), - ), + ) - persistToken: Effect.fn("AccountRepo.persistToken")((input) => - db((db) => + const persistToken = Effect.fn("AccountRepo.persistToken")((input) => + query((db) => db .update(AccountTable) .set({ @@ -113,34 +120,41 @@ export class AccountRepo extends ServiceMap.Service< .where(eq(AccountTable.id, input.accountID)) .run(), ).pipe(Effect.asVoid), - ), + ) - persistAccount: Effect.fn("AccountRepo.persistAccount")((input) => { - const orgID = Option.getOrNull(input.orgID) - return db((db) => - Database.transaction((tx) => { - tx.insert(AccountTable) - .values({ - id: input.id, - email: input.email, - url: input.url, + const persistAccount = Effect.fn("AccountRepo.persistAccount")((input) => + tx((db) => { + db.insert(AccountTable) + .values({ + id: input.id, + email: input.email, + url: input.url, + access_token: input.accessToken, + refresh_token: input.refreshToken, + token_expiry: input.expiry, + }) + .onConflictDoUpdate({ + target: AccountTable.id, + set: { access_token: input.accessToken, refresh_token: input.refreshToken, token_expiry: input.expiry, - }) - .onConflictDoUpdate({ - target: AccountTable.id, - set: { - access_token: input.accessToken, - refresh_token: input.refreshToken, - token_expiry: input.expiry, - }, - }) - .run() - setState(tx, input.id, orgID) - }), - ).pipe(Effect.asVoid) - }), + }, + }) + .run() + void state(db, input.id, input.orgID) + }).pipe(Effect.asVoid), + ) + + return AccountRepo.of({ + active, + list, + remove, + use, + getRow, + persistToken, + persistAccount, + }) }), ) } diff --git a/packages/opencode/src/account/schema.ts b/packages/opencode/src/account/schema.ts index 49c37932ff..6b62484ab6 100644 --- a/packages/opencode/src/account/schema.ts +++ b/packages/opencode/src/account/schema.ts @@ -20,6 +20,24 @@ export const AccessToken = Schema.String.pipe( ) export type AccessToken = Schema.Schema.Type<typeof AccessToken> +export const RefreshToken = Schema.String.pipe( + Schema.brand("RefreshToken"), + withStatics((s) => ({ make: (token: string) => s.makeUnsafe(token) })), +) +export type RefreshToken = Schema.Schema.Type<typeof RefreshToken> + +export const DeviceCode = Schema.String.pipe( + Schema.brand("DeviceCode"), + withStatics((s) => ({ make: (code: string) => s.makeUnsafe(code) })), +) +export type DeviceCode = Schema.Schema.Type<typeof DeviceCode> + +export const UserCode = Schema.String.pipe( + Schema.brand("UserCode"), + withStatics((s) => ({ make: (code: string) => s.makeUnsafe(code) })), +) +export type UserCode = Schema.Schema.Type<typeof UserCode> + export class Account extends Schema.Class<Account>("Account")({ id: AccountID, email: Schema.String, @@ -45,12 +63,12 @@ export class AccountServiceError extends Schema.TaggedErrorClass<AccountServiceE export type AccountError = AccountRepoError | AccountServiceError export class Login extends Schema.Class<Login>("Login")({ - code: Schema.String, - user: Schema.String, + code: DeviceCode, + user: UserCode, url: Schema.String, server: Schema.String, - expiry: Schema.Number, - interval: Schema.Number, + expiry: Schema.Duration, + interval: Schema.Duration, }) {} export class PollSuccess extends Schema.TaggedClass<PollSuccess>()("PollSuccess", { diff --git a/packages/opencode/src/account/service.ts b/packages/opencode/src/account/service.ts index ab1de72557..87e95c8f44 100644 --- a/packages/opencode/src/account/service.ts +++ b/packages/opencode/src/account/service.ts @@ -1,11 +1,5 @@ -import { Clock, Effect, Layer, Option, Schema, ServiceMap } from "effect" -import { - FetchHttpClient, - HttpClient, - HttpClientError, - HttpClientRequest, - HttpClientResponse, -} from "effect/unstable/http" +import { Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, ServiceMap } from "effect" +import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { withTransientReadRetry } from "@/util/effect-http-client" import { AccountRepo, type AccountRow } from "./repo" @@ -14,6 +8,8 @@ import { AccessToken, Account, AccountID, + DeviceCode, + RefreshToken, AccountServiceError, Login, Org, @@ -25,83 +21,101 @@ import { type PollResult, PollSlow, PollSuccess, + UserCode, } from "./schema" export * from "./schema" export type AccountOrgs = { account: Account - orgs: Org[] + orgs: readonly Org[] } -const RemoteOrg = Schema.Struct({ - id: Schema.optional(OrgID), - name: Schema.optional(Schema.String), -}) - -const RemoteOrgs = Schema.Array(RemoteOrg) - -const RemoteConfig = Schema.Struct({ +class RemoteConfig extends Schema.Class<RemoteConfig>("RemoteConfig")({ config: Schema.Record(Schema.String, Schema.Json), -}) +}) {} -const TokenRefresh = Schema.Struct({ - access_token: Schema.String, - refresh_token: Schema.optional(Schema.String), - expires_in: Schema.optional(Schema.Number), -}) +const DurationFromSeconds = Schema.Number.pipe( + Schema.decodeTo(Schema.Duration, { + decode: SchemaGetter.transform((n) => Duration.seconds(n)), + encode: SchemaGetter.transform((d) => Duration.toSeconds(d)), + }), +) -const DeviceCode = Schema.Struct({ - device_code: Schema.String, - user_code: Schema.String, +class TokenRefresh extends Schema.Class<TokenRefresh>("TokenRefresh")({ + access_token: AccessToken, + refresh_token: RefreshToken, + expires_in: DurationFromSeconds, +}) {} + +class DeviceAuth extends Schema.Class<DeviceAuth>("DeviceAuth")({ + device_code: DeviceCode, + user_code: UserCode, verification_uri_complete: Schema.String, - expires_in: Schema.Number, - interval: Schema.Number, -}) + expires_in: DurationFromSeconds, + interval: DurationFromSeconds, +}) {} -const DeviceToken = Schema.Struct({ - access_token: Schema.optional(Schema.String), - refresh_token: Schema.optional(Schema.String), - expires_in: Schema.optional(Schema.Number), - error: Schema.optional(Schema.String), - error_description: Schema.optional(Schema.String), -}) +class DeviceTokenSuccess extends Schema.Class<DeviceTokenSuccess>("DeviceTokenSuccess")({ + access_token: AccessToken, + refresh_token: RefreshToken, + token_type: Schema.Literal("Bearer"), + expires_in: DurationFromSeconds, +}) {} -const User = Schema.Struct({ - id: Schema.optional(AccountID), - email: Schema.optional(Schema.String), -}) +class DeviceTokenError extends Schema.Class<DeviceTokenError>("DeviceTokenError")({ + error: Schema.String, + error_description: Schema.String, +}) { + toPollResult(): PollResult { + if (this.error === "authorization_pending") return new PollPending() + if (this.error === "slow_down") return new PollSlow() + if (this.error === "expired_token") return new PollExpired() + if (this.error === "access_denied") return new PollDenied() + return new PollError({ cause: this.error }) + } +} -const ClientId = Schema.Struct({ client_id: Schema.String }) +const DeviceToken = Schema.Union([DeviceTokenSuccess, DeviceTokenError]) -const DeviceTokenRequest = Schema.Struct({ +class User extends Schema.Class<User>("User")({ + id: AccountID, + email: Schema.String, +}) {} + +class ClientId extends Schema.Class<ClientId>("ClientId")({ client_id: Schema.String }) {} + +class DeviceTokenRequest extends Schema.Class<DeviceTokenRequest>("DeviceTokenRequest")({ grant_type: Schema.String, - device_code: Schema.String, + device_code: DeviceCode, client_id: Schema.String, -}) +}) {} + +class TokenRefreshRequest extends Schema.Class<TokenRefreshRequest>("TokenRefreshRequest")({ + grant_type: Schema.String, + refresh_token: RefreshToken, + client_id: Schema.String, +}) {} const clientId = "opencode-cli" -const toAccountServiceError = (message: string, cause?: unknown) => new AccountServiceError({ message, cause }) - const mapAccountServiceError = - (operation: string, message = "Account service operation failed") => + (message = "Account service operation failed") => <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, AccountServiceError, R> => effect.pipe( - Effect.mapError((error) => - error instanceof AccountServiceError ? error : toAccountServiceError(`${message} (${operation})`, error), + Effect.mapError((cause) => + cause instanceof AccountServiceError ? cause : new AccountServiceError({ message, cause }), ), ) -export class AccountService extends ServiceMap.Service< - AccountService, - { +export namespace AccountService { + export interface Service { readonly active: () => Effect.Effect<Option.Option<Account>, AccountError> readonly list: () => Effect.Effect<Account[], AccountError> - readonly orgsByAccount: () => Effect.Effect<AccountOrgs[], AccountError> + readonly orgsByAccount: () => Effect.Effect<readonly AccountOrgs[], AccountError> readonly remove: (accountID: AccountID) => Effect.Effect<void, AccountError> readonly use: (accountID: AccountID, orgID: Option.Option<OrgID>) => Effect.Effect<void, AccountError> - readonly orgs: (accountID: AccountID) => Effect.Effect<Org[], AccountError> + readonly orgs: (accountID: AccountID) => Effect.Effect<readonly Org[], AccountError> readonly config: ( accountID: AccountID, orgID: OrgID, @@ -110,80 +124,98 @@ export class AccountService extends ServiceMap.Service< readonly login: (url: string) => Effect.Effect<Login, AccountError> readonly poll: (input: Login) => Effect.Effect<PollResult, AccountError> } ->()("@opencode/Account") { +} + +export class AccountService extends ServiceMap.Service<AccountService, AccountService.Service>()("@opencode/Account") { static readonly layer: Layer.Layer<AccountService, never, AccountRepo | HttpClient.HttpClient> = Layer.effect( AccountService, Effect.gen(function* () { const repo = yield* AccountRepo const http = yield* HttpClient.HttpClient const httpRead = withTransientReadRetry(http) + const httpOk = HttpClient.filterStatusOk(http) + const httpReadOk = HttpClient.filterStatusOk(httpRead) - const execute = (operation: string, request: HttpClientRequest.HttpClientRequest) => - http.execute(request).pipe(mapAccountServiceError(operation, "HTTP request failed")) + const executeRead = (request: HttpClientRequest.HttpClientRequest) => + httpRead.execute(request).pipe(mapAccountServiceError("HTTP request failed")) - const executeRead = (operation: string, request: HttpClientRequest.HttpClientRequest) => - httpRead.execute(request).pipe(mapAccountServiceError(operation, "HTTP request failed")) + const executeReadOk = (request: HttpClientRequest.HttpClientRequest) => + httpReadOk.execute(request).pipe(mapAccountServiceError("HTTP request failed")) - const executeEffect = <E>(operation: string, request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) => + const executeEffectOk = <E>(request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) => request.pipe( - Effect.flatMap((req) => http.execute(req)), - mapAccountServiceError(operation, "HTTP request failed"), + Effect.flatMap((req) => httpOk.execute(req)), + mapAccountServiceError("HTTP request failed"), ) - const okOrNone = (operation: string, response: HttpClientResponse.HttpClientResponse) => - HttpClientResponse.filterStatusOk(response).pipe( - Effect.map(Option.some), - Effect.catch((error) => - HttpClientError.isHttpClientError(error) && error.reason._tag === "StatusCodeError" - ? Effect.succeed(Option.none<HttpClientResponse.HttpClientResponse>()) - : Effect.fail(error), - ), - mapAccountServiceError(operation), - ) - - const tokenForRow = Effect.fn("AccountService.tokenForRow")(function* (found: AccountRow) { + // Returns a usable access token for a stored account row, refreshing and + // persisting it when the cached token has expired. + const resolveToken = Effect.fnUntraced(function* (row: AccountRow) { const now = yield* Clock.currentTimeMillis - if (found.token_expiry && found.token_expiry > now) return Option.some(AccessToken.make(found.access_token)) + if (row.token_expiry && row.token_expiry > now) return row.access_token - const response = yield* execute( - "token.refresh", - HttpClientRequest.post(`${found.url}/oauth/token`).pipe( + const response = yield* executeEffectOk( + HttpClientRequest.post(`${row.url}/auth/device/token`).pipe( HttpClientRequest.acceptJson, - HttpClientRequest.bodyUrlParams({ - grant_type: "refresh_token", - refresh_token: found.refresh_token, - }), + HttpClientRequest.schemaBodyJson(TokenRefreshRequest)( + new TokenRefreshRequest({ + grant_type: "refresh_token", + refresh_token: row.refresh_token, + client_id: clientId, + }), + ), ), ) - const ok = yield* okOrNone("token.refresh", response) - if (Option.isNone(ok)) return Option.none() - - const parsed = yield* HttpClientResponse.schemaBodyJson(TokenRefresh)(ok.value).pipe( - mapAccountServiceError("token.refresh", "Failed to decode response"), + const parsed = yield* HttpClientResponse.schemaBodyJson(TokenRefresh)(response).pipe( + mapAccountServiceError("Failed to decode response"), ) - const expiry = Option.fromNullishOr(parsed.expires_in).pipe(Option.map((e) => now + e * 1000)) + const expiry = Option.some(now + Duration.toMillis(parsed.expires_in)) yield* repo.persistToken({ - accountID: AccountID.make(found.id), + accountID: row.id, accessToken: parsed.access_token, - refreshToken: parsed.refresh_token ?? found.refresh_token, + refreshToken: parsed.refresh_token, expiry, }) - return Option.some(AccessToken.make(parsed.access_token)) + return parsed.access_token }) - const resolveAccess = Effect.fn("AccountService.resolveAccess")(function* (accountID: AccountID) { + const resolveAccess = Effect.fnUntraced(function* (accountID: AccountID) { const maybeAccount = yield* repo.getRow(accountID) - if (Option.isNone(maybeAccount)) return Option.none<{ account: AccountRow; accessToken: AccessToken }>() + if (Option.isNone(maybeAccount)) return Option.none() const account = maybeAccount.value - const accessToken = yield* tokenForRow(account) - if (Option.isNone(accessToken)) return Option.none<{ account: AccountRow; accessToken: AccessToken }>() + const accessToken = yield* resolveToken(account) + return Option.some({ account, accessToken }) + }) - return Option.some({ account, accessToken: accessToken.value }) + const fetchOrgs = Effect.fnUntraced(function* (url: string, accessToken: AccessToken) { + const response = yield* executeReadOk( + HttpClientRequest.get(`${url}/api/orgs`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.bearerToken(accessToken), + ), + ) + + return yield* HttpClientResponse.schemaBodyJson(Schema.Array(Org))(response).pipe( + mapAccountServiceError("Failed to decode response"), + ) + }) + + const fetchUser = Effect.fnUntraced(function* (url: string, accessToken: AccessToken) { + const response = yield* executeReadOk( + HttpClientRequest.get(`${url}/api/user`).pipe( + HttpClientRequest.acceptJson, + HttpClientRequest.bearerToken(accessToken), + ), + ) + + return yield* HttpClientResponse.schemaBodyJson(User)(response).pipe( + mapAccountServiceError("Failed to decode response"), + ) }) const token = Effect.fn("AccountService.token")((accountID: AccountID) => @@ -211,23 +243,7 @@ export class AccountService extends ServiceMap.Service< const { account, accessToken } = resolved.value - const response = yield* executeRead( - "orgs", - HttpClientRequest.get(`${account.url}/api/orgs`).pipe( - HttpClientRequest.acceptJson, - HttpClientRequest.bearerToken(accessToken), - ), - ) - - const ok = yield* okOrNone("orgs", response) - if (Option.isNone(ok)) return [] - - const orgs = yield* HttpClientResponse.schemaBodyJson(RemoteOrgs)(ok.value).pipe( - mapAccountServiceError("orgs", "Failed to decode response"), - ) - return orgs - .filter((org) => org.id !== undefined && org.name !== undefined) - .map((org) => new Org({ id: org.id!, name: org.name! })) + return yield* fetchOrgs(account.url, accessToken) }) const config = Effect.fn("AccountService.config")(function* (accountID: AccountID, orgID: OrgID) { @@ -237,7 +253,6 @@ export class AccountService extends ServiceMap.Service< const { account, accessToken } = resolved.value const response = yield* executeRead( - "config", HttpClientRequest.get(`${account.url}/api/config`).pipe( HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(accessToken), @@ -245,32 +260,26 @@ export class AccountService extends ServiceMap.Service< ), ) - const ok = yield* okOrNone("config", response) - if (Option.isNone(ok)) return Option.none() + if (response.status === 404) return Option.none() - const parsed = yield* HttpClientResponse.schemaBodyJson(RemoteConfig)(ok.value).pipe( - mapAccountServiceError("config", "Failed to decode response"), + const ok = yield* HttpClientResponse.filterStatusOk(response).pipe(mapAccountServiceError()) + + const parsed = yield* HttpClientResponse.schemaBodyJson(RemoteConfig)(ok).pipe( + mapAccountServiceError("Failed to decode response"), ) return Option.some(parsed.config) }) const login = Effect.fn("AccountService.login")(function* (server: string) { - const response = yield* executeEffect( - "login", + const response = yield* executeEffectOk( HttpClientRequest.post(`${server}/auth/device/code`).pipe( HttpClientRequest.acceptJson, - HttpClientRequest.schemaBodyJson(ClientId)({ client_id: clientId }), + HttpClientRequest.schemaBodyJson(ClientId)(new ClientId({ client_id: clientId })), ), ) - const ok = yield* okOrNone("login", response) - if (Option.isNone(ok)) { - const body = yield* response.text.pipe(Effect.orElseSucceed(() => "")) - return yield* toAccountServiceError(`Failed to initiate device flow: ${body || response.status}`) - } - - const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceCode)(ok.value).pipe( - mapAccountServiceError("login", "Failed to decode response"), + const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceAuth)(response).pipe( + mapAccountServiceError("Failed to decode response"), ) return new Login({ code: parsed.device_code, @@ -283,91 +292,49 @@ export class AccountService extends ServiceMap.Service< }) const poll = Effect.fn("AccountService.poll")(function* (input: Login) { - const response = yield* executeEffect( - "poll", + const response = yield* executeEffectOk( HttpClientRequest.post(`${input.server}/auth/device/token`).pipe( HttpClientRequest.acceptJson, - HttpClientRequest.schemaBodyJson(DeviceTokenRequest)({ - grant_type: "urn:ietf:params:oauth:grant-type:device_code", - device_code: input.code, - client_id: clientId, - }), + HttpClientRequest.schemaBodyJson(DeviceTokenRequest)( + new DeviceTokenRequest({ + grant_type: "urn:ietf:params:oauth:grant-type:device_code", + device_code: input.code, + client_id: clientId, + }), + ), ), ) const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceToken)(response).pipe( - mapAccountServiceError("poll", "Failed to decode response"), + mapAccountServiceError("Failed to decode response"), ) - if (!parsed.access_token) { - if (parsed.error === "authorization_pending") return new PollPending() - if (parsed.error === "slow_down") return new PollSlow() - if (parsed.error === "expired_token") return new PollExpired() - if (parsed.error === "access_denied") return new PollDenied() - return new PollError({ cause: parsed.error }) - } + if (parsed instanceof DeviceTokenError) return parsed.toPollResult() + const accessToken = parsed.access_token - const access = parsed.access_token + const user = fetchUser(input.server, accessToken) + const orgs = fetchOrgs(input.server, accessToken) - const fetchUser = executeRead( - "poll.user", - HttpClientRequest.get(`${input.server}/api/user`).pipe( - HttpClientRequest.acceptJson, - HttpClientRequest.bearerToken(access), - ), - ).pipe( - Effect.flatMap((r) => - HttpClientResponse.schemaBodyJson(User)(r).pipe( - mapAccountServiceError("poll.user", "Failed to decode response"), - ), - ), - ) + const [account, remoteOrgs] = yield* Effect.all([user, orgs], { concurrency: 2 }) - const fetchOrgs = executeRead( - "poll.orgs", - HttpClientRequest.get(`${input.server}/api/orgs`).pipe( - HttpClientRequest.acceptJson, - HttpClientRequest.bearerToken(access), - ), - ).pipe( - Effect.flatMap((r) => - HttpClientResponse.schemaBodyJson(RemoteOrgs)(r).pipe( - mapAccountServiceError("poll.orgs", "Failed to decode response"), - ), - ), - ) - - const [user, remoteOrgs] = yield* Effect.all([fetchUser, fetchOrgs], { concurrency: 2 }) - - const userId = user.id - const userEmail = user.email - - if (!userId || !userEmail) { - return new PollError({ cause: "No id or email in response" }) - } - - const firstOrgID = remoteOrgs.length > 0 ? Option.fromNullishOr(remoteOrgs[0].id) : Option.none() + // TODO: When there are multiple orgs, let the user choose + const firstOrgID = remoteOrgs.length > 0 ? Option.some(remoteOrgs[0].id) : Option.none<OrgID>() const now = yield* Clock.currentTimeMillis - const expiry = now + (parsed.expires_in ?? 0) * 1000 - const refresh = parsed.refresh_token ?? "" - if (!refresh) { - yield* Effect.logWarning( - "Server did not return a refresh token — session may expire without ability to refresh", - ) - } + const expiry = now + Duration.toMillis(parsed.expires_in) + const refreshToken = parsed.refresh_token yield* repo.persistAccount({ - id: userId, - email: userEmail, + id: account.id, + email: account.email, url: input.server, - accessToken: access, - refreshToken: refresh, + accessToken, + refreshToken, expiry, orgID: firstOrgID, }) - return new PollSuccess({ email: userEmail }) + return new PollSuccess({ email: account.email }) }) return AccountService.of({ diff --git a/packages/opencode/src/cli/cmd/account.ts b/packages/opencode/src/cli/cmd/account.ts index 7e9f893a8f..dd0834a3d8 100644 --- a/packages/opencode/src/cli/cmd/account.ts +++ b/packages/opencode/src/cli/cmd/account.ts @@ -24,17 +24,17 @@ const loginEffect = Effect.fn("login")(function* (url: string) { const s = Prompt.spinner() yield* s.start("Waiting for authorization...") - const poll = (wait: number): Effect.Effect<PollResult, AccountError> => + const poll = (wait: Duration.Duration): Effect.Effect<PollResult, AccountError> => Effect.gen(function* () { yield* Effect.sleep(wait) const result = yield* service.poll(login) if (result._tag === "PollPending") return yield* poll(wait) - if (result._tag === "PollSlow") return yield* poll(wait + 5000) + if (result._tag === "PollSlow") return yield* poll(Duration.sum(wait, Duration.seconds(5))) return result }) - const result = yield* poll(login.interval * 1000).pipe( - Effect.timeout(Duration.seconds(login.expiry)), + const result = yield* poll(login.interval).pipe( + Effect.timeout(login.expiry), Effect.catchTag("TimeoutError", () => Effect.succeed(new PollExpired())), ) diff --git a/packages/opencode/test/account/repo.test.ts b/packages/opencode/test/account/repo.test.ts index ecc392ead5..74a6d7a570 100644 --- a/packages/opencode/test/account/repo.test.ts +++ b/packages/opencode/test/account/repo.test.ts @@ -2,7 +2,7 @@ import { expect } from "bun:test" import { Effect, Layer, Option } from "effect" import { AccountRepo } from "../../src/account/repo" -import { AccountID, OrgID } from "../../src/account/schema" +import { AccessToken, AccountID, OrgID, RefreshToken } from "../../src/account/schema" import { Database } from "../../src/storage/db" import { testEffect } from "../fixture/effect" @@ -41,8 +41,8 @@ it.effect( id, email: "test@example.com", url: "https://control.example.com", - accessToken: "at_123", - refreshToken: "rt_456", + accessToken: AccessToken.make("at_123"), + refreshToken: RefreshToken.make("rt_456"), expiry: Date.now() + 3600_000, orgID: Option.some(OrgID.make("org-1")), }), @@ -51,7 +51,7 @@ it.effect( const row = yield* AccountRepo.use((r) => r.getRow(id)) expect(Option.isSome(row)).toBe(true) const value = Option.getOrThrow(row) - expect(value.id).toBe("user-1") + expect(value.id).toBe(AccountID.make("user-1")) expect(value.email).toBe("test@example.com") const active = yield* AccountRepo.use((r) => r.active()) @@ -70,8 +70,8 @@ it.effect( id: id1, email: "first@example.com", url: "https://control.example.com", - accessToken: "at_1", - refreshToken: "rt_1", + accessToken: AccessToken.make("at_1"), + refreshToken: RefreshToken.make("rt_1"), expiry: Date.now() + 3600_000, orgID: Option.some(OrgID.make("org-1")), }), @@ -82,8 +82,8 @@ it.effect( id: id2, email: "second@example.com", url: "https://control.example.com", - accessToken: "at_2", - refreshToken: "rt_2", + accessToken: AccessToken.make("at_2"), + refreshToken: RefreshToken.make("rt_2"), expiry: Date.now() + 3600_000, orgID: Option.some(OrgID.make("org-2")), }), @@ -108,8 +108,8 @@ it.effect( id: id1, email: "a@example.com", url: "https://control.example.com", - accessToken: "at_1", - refreshToken: "rt_1", + accessToken: AccessToken.make("at_1"), + refreshToken: RefreshToken.make("rt_1"), expiry: Date.now() + 3600_000, orgID: Option.none(), }), @@ -120,8 +120,8 @@ it.effect( id: id2, email: "b@example.com", url: "https://control.example.com", - accessToken: "at_2", - refreshToken: "rt_2", + accessToken: AccessToken.make("at_2"), + refreshToken: RefreshToken.make("rt_2"), expiry: Date.now() + 3600_000, orgID: Option.some(OrgID.make("org-1")), }), @@ -143,8 +143,8 @@ it.effect( id, email: "test@example.com", url: "https://control.example.com", - accessToken: "at_1", - refreshToken: "rt_1", + accessToken: AccessToken.make("at_1"), + refreshToken: RefreshToken.make("rt_1"), expiry: Date.now() + 3600_000, orgID: Option.none(), }), @@ -168,8 +168,8 @@ it.effect( id: id1, email: "first@example.com", url: "https://control.example.com", - accessToken: "at_1", - refreshToken: "rt_1", + accessToken: AccessToken.make("at_1"), + refreshToken: RefreshToken.make("rt_1"), expiry: Date.now() + 3600_000, orgID: Option.none(), }), @@ -180,8 +180,8 @@ it.effect( id: id2, email: "second@example.com", url: "https://control.example.com", - accessToken: "at_2", - refreshToken: "rt_2", + accessToken: AccessToken.make("at_2"), + refreshToken: RefreshToken.make("rt_2"), expiry: Date.now() + 3600_000, orgID: Option.none(), }), @@ -208,8 +208,8 @@ it.effect( id, email: "test@example.com", url: "https://control.example.com", - accessToken: "old_token", - refreshToken: "old_refresh", + accessToken: AccessToken.make("old_token"), + refreshToken: RefreshToken.make("old_refresh"), expiry: 1000, orgID: Option.none(), }), @@ -219,16 +219,16 @@ it.effect( yield* AccountRepo.use((r) => r.persistToken({ accountID: id, - accessToken: "new_token", - refreshToken: "new_refresh", + accessToken: AccessToken.make("new_token"), + refreshToken: RefreshToken.make("new_refresh"), expiry: Option.some(expiry), }), ) const row = yield* AccountRepo.use((r) => r.getRow(id)) const value = Option.getOrThrow(row) - expect(value.access_token).toBe("new_token") - expect(value.refresh_token).toBe("new_refresh") + expect(value.access_token).toBe(AccessToken.make("new_token")) + expect(value.refresh_token).toBe(RefreshToken.make("new_refresh")) expect(value.token_expiry).toBe(expiry) }), ) @@ -243,8 +243,8 @@ it.effect( id, email: "test@example.com", url: "https://control.example.com", - accessToken: "old_token", - refreshToken: "old_refresh", + accessToken: AccessToken.make("old_token"), + refreshToken: RefreshToken.make("old_refresh"), expiry: 1000, orgID: Option.none(), }), @@ -253,8 +253,8 @@ it.effect( yield* AccountRepo.use((r) => r.persistToken({ accountID: id, - accessToken: "new_token", - refreshToken: "new_refresh", + accessToken: AccessToken.make("new_token"), + refreshToken: RefreshToken.make("new_refresh"), expiry: Option.none(), }), ) @@ -274,8 +274,8 @@ it.effect( id, email: "test@example.com", url: "https://control.example.com", - accessToken: "at_v1", - refreshToken: "rt_v1", + accessToken: AccessToken.make("at_v1"), + refreshToken: RefreshToken.make("rt_v1"), expiry: 1000, orgID: Option.some(OrgID.make("org-1")), }), @@ -286,8 +286,8 @@ it.effect( id, email: "test@example.com", url: "https://control.example.com", - accessToken: "at_v2", - refreshToken: "rt_v2", + accessToken: AccessToken.make("at_v2"), + refreshToken: RefreshToken.make("rt_v2"), expiry: 2000, orgID: Option.some(OrgID.make("org-2")), }), @@ -298,7 +298,7 @@ it.effect( const row = yield* AccountRepo.use((r) => r.getRow(id)) const value = Option.getOrThrow(row) - expect(value.access_token).toBe("at_v2") + expect(value.access_token).toBe(AccessToken.make("at_v2")) const active = yield* AccountRepo.use((r) => r.active()) expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2")) @@ -315,8 +315,8 @@ it.effect( id, email: "test@example.com", url: "https://control.example.com", - accessToken: "at_1", - refreshToken: "rt_1", + accessToken: AccessToken.make("at_1"), + refreshToken: RefreshToken.make("rt_1"), expiry: Date.now() + 3600_000, orgID: Option.some(OrgID.make("org-1")), }), diff --git a/packages/opencode/test/account/service.test.ts b/packages/opencode/test/account/service.test.ts index 87f5b23f28..5caa33235a 100644 --- a/packages/opencode/test/account/service.test.ts +++ b/packages/opencode/test/account/service.test.ts @@ -1,10 +1,10 @@ import { expect } from "bun:test" -import { Effect, Layer, Option, Ref, Schema } from "effect" +import { Duration, Effect, Layer, Option, Ref, Schema } from "effect" import { HttpClient, HttpClientResponse } from "effect/unstable/http" import { AccountRepo } from "../../src/account/repo" import { AccountService } from "../../src/account/service" -import { AccountID, Login, Org, OrgID } from "../../src/account/schema" +import { AccessToken, AccountID, DeviceCode, Login, Org, OrgID, RefreshToken, UserCode } from "../../src/account/schema" import { Database } from "../../src/storage/db" import { testEffect } from "../fixture/effect" @@ -42,8 +42,8 @@ it.effect( id: AccountID.make("user-1"), email: "one@example.com", url: "https://one.example.com", - accessToken: "at_1", - refreshToken: "rt_1", + accessToken: AccessToken.make("at_1"), + refreshToken: RefreshToken.make("rt_1"), expiry: Date.now() + 60_000, orgID: Option.none(), }), @@ -54,8 +54,8 @@ it.effect( id: AccountID.make("user-2"), email: "two@example.com", url: "https://two.example.com", - accessToken: "at_2", - refreshToken: "rt_2", + accessToken: AccessToken.make("at_2"), + refreshToken: RefreshToken.make("rt_2"), expiry: Date.now() + 60_000, orgID: Option.none(), }), @@ -101,8 +101,8 @@ it.effect( id, email: "user@example.com", url: "https://one.example.com", - accessToken: "at_old", - refreshToken: "rt_old", + accessToken: AccessToken.make("at_old"), + refreshToken: RefreshToken.make("rt_old"), expiry: Date.now() - 1_000, orgID: Option.none(), }), @@ -110,7 +110,7 @@ it.effect( const client = HttpClient.make((req) => Effect.succeed( - req.url === "https://one.example.com/oauth/token" + req.url === "https://one.example.com/auth/device/token" ? json(req, { access_token: "at_new", refresh_token: "rt_new", @@ -127,8 +127,8 @@ it.effect( const row = yield* AccountRepo.use((r) => r.getRow(id)) const value = Option.getOrThrow(row) - expect(value.access_token).toBe("at_new") - expect(value.refresh_token).toBe("rt_new") + expect(value.access_token).toBe(AccessToken.make("at_new")) + expect(value.refresh_token).toBe(RefreshToken.make("rt_new")) expect(value.token_expiry).toBeGreaterThan(Date.now()) }), ) @@ -143,8 +143,8 @@ it.effect( id, email: "user@example.com", url: "https://one.example.com", - accessToken: "at_1", - refreshToken: "rt_1", + accessToken: AccessToken.make("at_1"), + refreshToken: RefreshToken.make("rt_1"), expiry: Date.now() + 60_000, orgID: Option.none(), }), @@ -180,12 +180,12 @@ it.effect( "poll stores the account and first org on success", Effect.gen(function* () { const login = new Login({ - code: "device-code", - user: "user-code", + code: DeviceCode.make("device-code"), + user: UserCode.make("user-code"), url: "https://one.example.com/verify", server: "https://one.example.com", - expiry: 600, - interval: 5, + expiry: Duration.seconds(600), + interval: Duration.seconds(5), }) const client = HttpClient.make((req) => @@ -194,6 +194,7 @@ it.effect( ? json(req, { access_token: "at_1", refresh_token: "rt_1", + token_type: "Bearer", expires_in: 60, }) : req.url === "https://one.example.com/api/user" From cf7ca9b2f7f13fabd87e2ff41264d12ddd4f85ff Mon Sep 17 00:00:00 2001 From: Chris Yang <18487241+ysm-dev@users.noreply.github.com> Date: Thu, 12 Mar 2026 03:40:06 +0900 Subject: [PATCH 084/200] fix(app): skip editor reconcile during IME composition (#17041) --- packages/app/src/components/prompt-input.tsx | 49 +++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 532edd3bcd..3ee8f43513 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -490,6 +490,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => { setComposing(false) } + const handleCompositionStart = () => { + setComposing(true) + } + + const handleCompositionEnd = () => { + setComposing(false) + requestAnimationFrame(() => { + if (composing()) return + reconcile(prompt.current().filter((part) => part.type !== "image")) + }) + } + const agentList = createMemo(() => sync.data.agent .filter((agent) => !agent.hidden && agent.mode !== "primary") @@ -680,24 +692,27 @@ export const PromptInput: Component<PromptInputProps> = (props) => { } } + const reconcile = (input: Prompt) => { + if (mirror.input) { + mirror.input = false + if (isNormalizedEditor()) return + + renderEditorWithCursor(input) + return + } + + const dom = parseFromDOM() + if (isNormalizedEditor() && isPromptEqual(input, dom)) return + + renderEditorWithCursor(input) + } + createEffect( on( () => prompt.current(), - (currentParts) => { - const inputParts = currentParts.filter((part) => part.type !== "image") - - if (mirror.input) { - mirror.input = false - if (isNormalizedEditor()) return - - renderEditorWithCursor(inputParts) - return - } - - const domParts = parseFromDOM() - if (isNormalizedEditor() && isPromptEqual(inputParts, domParts)) return - - renderEditorWithCursor(inputParts) + (parts) => { + if (composing()) return + reconcile(parts.filter((part) => part.type !== "image")) }, ), ) @@ -1208,8 +1223,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => { spellcheck={store.mode === "normal"} onInput={handleInput} onPaste={handlePaste} - onCompositionStart={() => setComposing(true)} - onCompositionEnd={() => setComposing(false)} + onCompositionStart={handleCompositionStart} + onCompositionEnd={handleCompositionEnd} onBlur={handleBlur} onKeyDown={handleKeyDown} classList={{ From c37f7b9d997fcbbf4f331e1965425375e7606ab6 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:42:29 -0500 Subject: [PATCH 085/200] fix(app): todos not clearing --- .../composer/session-composer-helpers.ts | 10 ++++ .../composer/session-composer-state.test.ts | 23 ++++++++ .../composer/session-composer-state.ts | 58 ++++++++++++++----- 3 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 packages/app/src/pages/session/composer/session-composer-helpers.ts diff --git a/packages/app/src/pages/session/composer/session-composer-helpers.ts b/packages/app/src/pages/session/composer/session-composer-helpers.ts new file mode 100644 index 0000000000..90c238af46 --- /dev/null +++ b/packages/app/src/pages/session/composer/session-composer-helpers.ts @@ -0,0 +1,10 @@ +export const todoState = (input: { + count: number + done: boolean + live: boolean +}): "hide" | "clear" | "open" | "close" => { + if (input.count === 0) return "hide" + if (!input.live) return "clear" + if (!input.done) return "open" + return "close" +} diff --git a/packages/app/src/pages/session/composer/session-composer-state.test.ts b/packages/app/src/pages/session/composer/session-composer-state.test.ts index 934d3152a9..f7c11715c2 100644 --- a/packages/app/src/pages/session/composer/session-composer-state.test.ts +++ b/packages/app/src/pages/session/composer/session-composer-state.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test" import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client" +import { todoState } from "./session-composer-helpers" import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree" const session = (input: { id: string; parentID?: string }) => @@ -103,3 +104,25 @@ describe("sessionQuestionRequest", () => { expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-grand") }) }) + +describe("todoState", () => { + test("hides when there are no todos", () => { + expect(todoState({ count: 0, done: false, live: true })).toBe("hide") + }) + + test("opens while the session is still working", () => { + expect(todoState({ count: 2, done: false, live: true })).toBe("open") + }) + + test("closes completed todos after a running turn", () => { + expect(todoState({ count: 2, done: true, live: true })).toBe("close") + }) + + test("clears stale todos when the turn ends", () => { + expect(todoState({ count: 2, done: false, live: false })).toBe("clear") + }) + + test("clears completed todos when the session is no longer live", () => { + expect(todoState({ count: 2, done: true, live: false })).toBe("clear") + }) +}) diff --git a/packages/app/src/pages/session/composer/session-composer-state.ts b/packages/app/src/pages/session/composer/session-composer-state.ts index f70bc4bbdd..a007e4c849 100644 --- a/packages/app/src/pages/session/composer/session-composer-state.ts +++ b/packages/app/src/pages/session/composer/session-composer-state.ts @@ -8,8 +8,11 @@ import { useLanguage } from "@/context/language" import { usePermission } from "@/context/permission" import { useSDK } from "@/context/sdk" import { useSync } from "@/context/sync" +import { todoState } from "./session-composer-helpers" import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree" +const idle = { type: "idle" as const } + export function createSessionComposerBlocked() { const params = useParams() const permission = usePermission() @@ -59,9 +62,22 @@ export function createSessionComposerState(options?: { closeMs?: number | (() => return globalSync.data.session_todo[id] ?? [] }) + const done = createMemo( + () => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"), + ) + + const status = createMemo(() => { + const id = params.id + if (!id) return idle + return sync.data.session_status[id] ?? idle + }) + + const busy = createMemo(() => status().type !== "idle") + const live = createMemo(() => busy() || blocked()) + const [store, setStore] = createStore({ responding: undefined as string | undefined, - dock: todos().length > 0, + dock: todos().length > 0 && live(), closing: false, opening: false, }) @@ -89,10 +105,6 @@ export function createSessionComposerState(options?: { closeMs?: number | (() => }) } - const done = createMemo( - () => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"), - ) - let timer: number | undefined let raf: number | undefined @@ -111,21 +123,42 @@ export function createSessionComposerState(options?: { closeMs?: number | (() => }, closeMs()) } + // Keep stale turn todos from reopening if the model never clears them. + const clear = () => { + const id = params.id + if (!id) return + globalSync.todo.set(id, []) + sync.set("todo", id, []) + } + createEffect( on( - () => [todos().length, done()] as const, - ([count, complete], prev) => { + () => [todos().length, done(), live()] as const, + ([count, complete, active]) => { if (raf) cancelAnimationFrame(raf) raf = undefined - if (count === 0) { + const next = todoState({ + count, + done: complete, + live: active, + }) + + if (next === "hide") { if (timer) window.clearTimeout(timer) timer = undefined setStore({ dock: false, closing: false, opening: false }) return } - if (!complete) { + if (next === "clear") { + if (timer) window.clearTimeout(timer) + timer = undefined + clear() + return + } + + if (next === "open") { if (timer) window.clearTimeout(timer) timer = undefined const hidden = !store.dock || store.closing @@ -142,13 +175,8 @@ export function createSessionComposerState(options?: { closeMs?: number | (() => return } - if (prev && prev[1]) { - if (store.closing && !timer) scheduleClose() - return - } - setStore({ dock: true, opening: false, closing: true }) - scheduleClose() + if (!timer) scheduleClose() }, ), ) From dbc00aa8e04cd9fbfb0534e24751c38232862244 Mon Sep 17 00:00:00 2001 From: Kit Langton <kit.langton@gmail.com> Date: Wed, 11 Mar 2026 16:44:26 -0400 Subject: [PATCH 086/200] feat(id): brand ProjectID through Drizzle and Zod schemas (#16948) --- packages/opencode/src/cli/cmd/import.ts | 4 +- packages/opencode/src/control-plane/types.ts | 3 +- .../src/control-plane/workspace.sql.ts | 2 + .../opencode/src/control-plane/workspace.ts | 3 +- packages/opencode/src/permission/next.ts | 3 +- packages/opencode/src/project/project.sql.ts | 3 +- packages/opencode/src/project/project.ts | 43 ++++++++++--------- packages/opencode/src/project/schema.ts | 16 +++++++ .../opencode/src/server/routes/project.ts | 3 +- packages/opencode/src/session/index.ts | 5 ++- packages/opencode/src/session/session.sql.ts | 2 + packages/opencode/src/worktree/index.ts | 5 ++- packages/opencode/test/config/config.test.ts | 3 +- .../opencode/test/project/project.test.ts | 9 ++-- .../test/storage/json-migration.test.ts | 17 ++++---- 15 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 packages/opencode/src/project/schema.ts diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index eb5964379a..cfea5fdbb3 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -86,7 +86,7 @@ export const ImportCommand = cmd({ await bootstrap(process.cwd(), async () => { let exportData: | { - info: Session.Info + info: SDKSession messages: Array<{ info: Message parts: Part[] @@ -152,7 +152,7 @@ export const ImportCommand = cmd({ return } - const row = { ...Session.toRow(exportData.info), project_id: Instance.project.id } + const row = Session.toRow({ ...exportData.info, projectID: Instance.project.id }) Database.use((db) => db .insert(SessionTable) diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts index 3d27757fd1..53a6c8a073 100644 --- a/packages/opencode/src/control-plane/types.ts +++ b/packages/opencode/src/control-plane/types.ts @@ -1,5 +1,6 @@ import z from "zod" import { Identifier } from "@/id/id" +import { ProjectID } from "@/project/schema" export const WorkspaceInfo = z.object({ id: Identifier.schema("workspace"), @@ -8,7 +9,7 @@ export const WorkspaceInfo = z.object({ name: z.string().nullable(), directory: z.string().nullable(), extra: z.unknown().nullable(), - projectID: z.string(), + projectID: ProjectID.zod, }) export type WorkspaceInfo = z.infer<typeof WorkspaceInfo> diff --git a/packages/opencode/src/control-plane/workspace.sql.ts b/packages/opencode/src/control-plane/workspace.sql.ts index 7639620691..eb7d21d1e8 100644 --- a/packages/opencode/src/control-plane/workspace.sql.ts +++ b/packages/opencode/src/control-plane/workspace.sql.ts @@ -1,5 +1,6 @@ import { sqliteTable, text } from "drizzle-orm/sqlite-core" import { ProjectTable } from "../project/project.sql" +import type { ProjectID } from "../project/schema" export const WorkspaceTable = sqliteTable("workspace", { id: text().primaryKey(), @@ -9,6 +10,7 @@ export const WorkspaceTable = sqliteTable("workspace", { directory: text(), extra: text({ mode: "json" }), project_id: text() + .$type<ProjectID>() .notNull() .references(() => ProjectTable.id, { onDelete: "cascade" }), }) diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 8c76fbdab9..f3af985cac 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -6,6 +6,7 @@ import { Project } from "@/project/project" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" import { Log } from "@/util/log" +import { ProjectID } from "@/project/schema" import { WorkspaceTable } from "./workspace.sql" import { getAdaptor } from "./adaptors" import { WorkspaceInfo } from "./types" @@ -48,7 +49,7 @@ export namespace Workspace { id: Identifier.schema("workspace").optional(), type: Info.shape.type, branch: Info.shape.branch, - projectID: Info.shape.projectID, + projectID: ProjectID.zod, extra: Info.shape.extra, }) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 1e1df62a3c..4e47e43c8f 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -7,6 +7,7 @@ import { Database, eq } from "@/storage/db" import { PermissionTable } from "@/session/session.sql" import { fn } from "@/util/fn" import { Log } from "@/util/log" +import { ProjectID } from "@/project/schema" import { Wildcard } from "@/util/wildcard" import os from "os" import z from "zod" @@ -90,7 +91,7 @@ export namespace PermissionNext { export type Reply = z.infer<typeof Reply> export const Approval = z.object({ - projectID: z.string(), + projectID: ProjectID.zod, patterns: z.string().array(), }) diff --git a/packages/opencode/src/project/project.sql.ts b/packages/opencode/src/project/project.sql.ts index 7f0f8ca532..efbc400b5e 100644 --- a/packages/opencode/src/project/project.sql.ts +++ b/packages/opencode/src/project/project.sql.ts @@ -1,8 +1,9 @@ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core" import { Timestamps } from "../storage/schema.sql" +import type { ProjectID } from "./schema" export const ProjectTable = sqliteTable("project", { - id: text().primaryKey(), + id: text().$type<ProjectID>().primaryKey(), worktree: text().notNull(), vcs: text(), name: text(), diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 68cece0a52..196dc8da61 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -15,6 +15,7 @@ import { existsSync } from "fs" import { git } from "../util/git" import { Glob } from "../util/glob" import { which } from "../util/which" +import { ProjectID } from "./schema" export namespace Project { const log = Log.create({ service: "project" }) @@ -33,7 +34,7 @@ export namespace Project { export const Info = z .object({ - id: z.string(), + id: ProjectID.zod, worktree: z.string(), vcs: z.literal("git").optional(), name: z.string().optional(), @@ -73,7 +74,7 @@ export namespace Project { ? { url: row.icon_url ?? undefined, color: row.icon_color ?? undefined } : undefined return { - id: row.id, + id: ProjectID.make(row.id), worktree: row.worktree, vcs: row.vcs ? Info.shape.vcs.parse(row.vcs) : undefined, name: row.name ?? undefined, @@ -91,6 +92,7 @@ export namespace Project { function readCachedId(dir: string) { return Filesystem.readText(path.join(dir, "opencode")) .then((x) => x.trim()) + .then(ProjectID.make) .catch(() => undefined) } @@ -111,7 +113,7 @@ export namespace Project { if (!gitBinary) { return { - id: id ?? "global", + id: id ?? ProjectID.global, worktree: sandbox, sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), @@ -130,7 +132,7 @@ export namespace Project { if (!worktree) { return { - id: id ?? "global", + id: id ?? ProjectID.global, worktree: sandbox, sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), @@ -160,14 +162,14 @@ export namespace Project { if (!roots) { return { - id: "global", + id: ProjectID.global, worktree: sandbox, sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), } } - id = roots[0] + id = roots[0] ? ProjectID.make(roots[0]) : undefined if (id) { await Filesystem.write(path.join(dotgit, "opencode"), id).catch(() => undefined) } @@ -175,7 +177,7 @@ export namespace Project { if (!id) { return { - id: "global", + id: ProjectID.global, worktree: sandbox, sandbox, vcs: "git", @@ -208,7 +210,7 @@ export namespace Project { } return { - id: "global", + id: ProjectID.global, worktree: "/", sandbox: "/", vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), @@ -228,7 +230,7 @@ export namespace Project { updated: Date.now(), }, } - if (data.id !== "global") { + if (data.id !== ProjectID.global) { await migrateFromGlobal(data.id, data.worktree) } return fresh @@ -308,12 +310,12 @@ export namespace Project { return } - async function migrateFromGlobal(id: string, worktree: string) { - const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, "global")).get()) + async function migrateFromGlobal(id: ProjectID, worktree: string) { + const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, ProjectID.global)).get()) if (!row) return const sessions = Database.use((db) => - db.select().from(SessionTable).where(eq(SessionTable.project_id, "global")).all(), + db.select().from(SessionTable).where(eq(SessionTable.project_id, ProjectID.global)).all(), ) if (sessions.length === 0) return @@ -323,14 +325,14 @@ export namespace Project { // Skip sessions that belong to a different directory if (row.directory && row.directory !== worktree) return - log.info("migrating session", { sessionID: row.id, from: "global", to: id }) + log.info("migrating session", { sessionID: row.id, from: ProjectID.global, to: id }) Database.use((db) => db.update(SessionTable).set({ project_id: id }).where(eq(SessionTable.id, row.id)).run()) }).catch((error) => { log.error("failed to migrate sessions from global to project", { error, projectId: id }) }) } - export function setInitialized(id: string) { + export function setInitialized(id: ProjectID) { Database.use((db) => db .update(ProjectTable) @@ -352,7 +354,7 @@ export namespace Project { ) } - export function get(id: string): Info | undefined { + export function get(id: ProjectID): Info | undefined { const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get()) if (!row) return undefined return fromRow(row) @@ -375,12 +377,13 @@ export namespace Project { export const update = fn( z.object({ - projectID: z.string(), + projectID: ProjectID.zod, name: z.string().optional(), icon: Info.shape.icon.optional(), commands: Info.shape.commands.optional(), }), async (input) => { + const id = ProjectID.make(input.projectID) const result = Database.use((db) => db .update(ProjectTable) @@ -391,7 +394,7 @@ export namespace Project { commands: input.commands, time_updated: Date.now(), }) - .where(eq(ProjectTable.id, input.projectID)) + .where(eq(ProjectTable.id, id)) .returning() .get(), ) @@ -407,7 +410,7 @@ export namespace Project { }, ) - export async function sandboxes(id: string) { + export async function sandboxes(id: ProjectID) { const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get()) if (!row) return [] const data = fromRow(row) @@ -419,7 +422,7 @@ export namespace Project { return valid } - export async function addSandbox(id: string, directory: string) { + export async function addSandbox(id: ProjectID, directory: string) { const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get()) if (!row) throw new Error(`Project not found: ${id}`) const sandboxes = [...row.sandboxes] @@ -443,7 +446,7 @@ export namespace Project { return data } - export async function removeSandbox(id: string, directory: string) { + export async function removeSandbox(id: ProjectID, directory: string) { const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get()) if (!row) throw new Error(`Project not found: ${id}`) const sandboxes = row.sandboxes.filter((s) => s !== directory) diff --git a/packages/opencode/src/project/schema.ts b/packages/opencode/src/project/schema.ts new file mode 100644 index 0000000000..adaa9a8314 --- /dev/null +++ b/packages/opencode/src/project/schema.ts @@ -0,0 +1,16 @@ +import { Schema } from "effect" +import z from "zod" + +import { withStatics } from "@/util/schema" + +const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectId")) + +export type ProjectID = typeof projectIdSchema.Type + +export const ProjectID = projectIdSchema.pipe( + withStatics((schema: typeof projectIdSchema) => ({ + global: schema.makeUnsafe("global"), + make: (id: string) => schema.makeUnsafe(id), + zod: z.string().pipe(z.custom<ProjectID>()), + })), +) diff --git a/packages/opencode/src/server/routes/project.ts b/packages/opencode/src/server/routes/project.ts index 85314df937..994d58b0ca 100644 --- a/packages/opencode/src/server/routes/project.ts +++ b/packages/opencode/src/server/routes/project.ts @@ -4,6 +4,7 @@ import { resolver } from "hono-openapi" import { Instance } from "../../project/instance" import { Project } from "../../project/project" import z from "zod" +import { ProjectID } from "../../project/schema" import { errors } from "../error" import { lazy } from "../../util/lazy" import { InstanceBootstrap } from "../../project/bootstrap" @@ -105,7 +106,7 @@ export const ProjectRoutes = lazy(() => ...errors(400, 404), }, }), - validator("param", z.object({ projectID: z.string() })), + validator("param", z.object({ projectID: ProjectID.zod })), validator("json", Project.update.schema.omit({ projectID: true })), async (c) => { const projectID = c.req.valid("param").projectID diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 5cc4d7da8d..ff499fe2e7 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -23,6 +23,7 @@ import { fn } from "@/util/fn" import { Command } from "../command" import { Snapshot } from "@/snapshot" import { WorkspaceContext } from "../control-plane/workspace-context" +import { ProjectID } from "../project/schema" import type { Provider } from "@/provider/provider" import { PermissionNext } from "@/permission/next" @@ -120,7 +121,7 @@ export namespace Session { .object({ id: Identifier.schema("session"), slug: z.string(), - projectID: z.string(), + projectID: ProjectID.zod, workspaceID: z.string().optional(), directory: z.string(), parentID: Identifier.schema("session").optional(), @@ -162,7 +163,7 @@ export namespace Session { export const ProjectInfo = z .object({ - id: z.string(), + id: ProjectID.zod, name: z.string().optional(), worktree: z.string(), }) diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index b3228f400f..0b62dbbd8c 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -3,6 +3,7 @@ import { ProjectTable } from "../project/project.sql" import type { MessageV2 } from "./message-v2" import type { Snapshot } from "../snapshot" import type { PermissionNext } from "../permission/next" +import type { ProjectID } from "../project/schema" import { Timestamps } from "../storage/schema.sql" type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID"> @@ -13,6 +14,7 @@ export const SessionTable = sqliteTable( { id: text().primaryKey(), project_id: text() + .$type<ProjectID>() .notNull() .references(() => ProjectTable.id, { onDelete: "cascade" }), workspace_id: text(), diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index aa5613010c..6ed0e48202 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -8,6 +8,7 @@ import { InstanceBootstrap } from "../project/bootstrap" import { Project } from "../project/project" import { Database, eq } from "../storage/db" import { ProjectTable } from "../project/project.sql" +import type { ProjectID } from "../project/schema" import { fn } from "../util/fn" import { Log } from "../util/log" import { Process } from "../util/process" @@ -310,7 +311,7 @@ export namespace Worktree { return false } - async function runStartScripts(directory: string, input: { projectID: string; extra?: string }) { + async function runStartScripts(directory: string, input: { projectID: ProjectID; extra?: string }) { const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, input.projectID)).get()) const project = row ? Project.fromRow(row) : undefined const startup = project?.commands?.start?.trim() ?? "" @@ -322,7 +323,7 @@ export namespace Worktree { return true } - function queueStartScripts(directory: string, input: { projectID: string; extra?: string }) { + function queueStartScripts(directory: string, input: { projectID: ProjectID; extra?: string }) { setTimeout(() => { const start = async () => { await runStartScripts(directory, input) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 80394fbff5..90727cf8a0 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -8,6 +8,7 @@ import path from "path" import fs from "fs/promises" import { pathToFileURL } from "url" import { Global } from "../../src/global" +import { ProjectID } from "../../src/project/schema" import { Filesystem } from "../../src/util/filesystem" // Get managed config directory from environment (set in preload.ts) @@ -44,7 +45,7 @@ async function check(map: (dir: string) => string) { const cfg = await Config.get() expect(cfg.snapshot).toBe(true) expect(Instance.directory).toBe(Filesystem.resolve(tmp.path)) - expect(Instance.project.id).not.toBe("global") + expect(Instance.project.id).not.toBe(ProjectID.global) }, }) } finally { diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index fef9e4190e..cc6b8dde5b 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -6,6 +6,7 @@ import path from "path" import { tmpdir } from "../fixture/fixture" import { Filesystem } from "../../src/util/filesystem" import { GlobalBus } from "../../src/bus/global" +import { ProjectID } from "../../src/project/schema" Log.init({ print: false }) @@ -74,7 +75,7 @@ describe("Project.fromDirectory", () => { const { project } = await p.fromDirectory(tmp.path) expect(project).toBeDefined() - expect(project.id).toBe("global") + expect(project.id).toBe(ProjectID.global) expect(project.vcs).toBe("git") expect(project.worktree).toBe(tmp.path) @@ -90,7 +91,7 @@ describe("Project.fromDirectory", () => { const { project } = await p.fromDirectory(tmp.path) expect(project).toBeDefined() - expect(project.id).not.toBe("global") + expect(project.id).not.toBe(ProjectID.global) expect(project.vcs).toBe("git") expect(project.worktree).toBe(tmp.path) @@ -107,7 +108,7 @@ describe("Project.fromDirectory", () => { await withMode("rev-list-fail", async () => { const { project } = await p.fromDirectory(tmp.path) expect(project.vcs).toBe("git") - expect(project.id).toBe("global") + expect(project.id).toBe(ProjectID.global) expect(project.worktree).toBe(tmp.path) }) }) @@ -301,7 +302,7 @@ describe("Project.update", () => { await expect( Project.update({ - projectID: "nonexistent-project-id", + projectID: ProjectID.make("nonexistent-project-id"), name: "Should Fail", }), ).rejects.toThrow("Project not found: nonexistent-project-id") diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts index 40dd611453..76dfb8b450 100644 --- a/packages/opencode/test/storage/json-migration.test.ts +++ b/packages/opencode/test/storage/json-migration.test.ts @@ -8,6 +8,7 @@ import { readFileSync, readdirSync } from "fs" import { JsonMigration } from "../../src/storage/json-migration" import { Global } from "../../src/global" import { ProjectTable } from "../../src/project/project.sql" +import { ProjectID } from "../../src/project/schema" import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql" import { SessionShareTable } from "../../src/share/share.sql" @@ -123,7 +124,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const projects = db.select().from(ProjectTable).all() expect(projects.length).toBe(1) - expect(projects[0].id).toBe("proj_test123abc") + expect(projects[0].id).toBe(ProjectID.make("proj_test123abc")) expect(projects[0].worktree).toBe("/test/path") expect(projects[0].name).toBe("Test Project") expect(projects[0].sandboxes).toEqual(["/test/sandbox"]) @@ -148,7 +149,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const projects = db.select().from(ProjectTable).all() expect(projects.length).toBe(1) - expect(projects[0].id).toBe("proj_filename") // Uses filename, not JSON id + expect(projects[0].id).toBe(ProjectID.make("proj_filename")) // Uses filename, not JSON id }) test("migrates project with commands", async () => { @@ -169,7 +170,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const projects = db.select().from(ProjectTable).all() expect(projects.length).toBe(1) - expect(projects[0].id).toBe("proj_with_commands") + expect(projects[0].id).toBe(ProjectID.make("proj_with_commands")) expect(projects[0].commands).toEqual({ start: "npm run dev" }) }) @@ -190,7 +191,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const projects = db.select().from(ProjectTable).all() expect(projects.length).toBe(1) - expect(projects[0].id).toBe("proj_no_commands") + expect(projects[0].id).toBe(ProjectID.make("proj_no_commands")) expect(projects[0].commands).toBeNull() }) @@ -220,7 +221,7 @@ describe("JSON to SQLite migration", () => { const sessions = db.select().from(SessionTable).all() expect(sessions.length).toBe(1) expect(sessions[0].id).toBe("ses_test456def") - expect(sessions[0].project_id).toBe("proj_test123abc") + expect(sessions[0].project_id).toBe(ProjectID.make("proj_test123abc")) expect(sessions[0].slug).toBe("test-session") expect(sessions[0].title).toBe("Test Session Title") expect(sessions[0].summary_additions).toBe(10) @@ -426,7 +427,7 @@ describe("JSON to SQLite migration", () => { const sessions = db.select().from(SessionTable).all() expect(sessions.length).toBe(1) expect(sessions[0].id).toBe("ses_migrated") - expect(sessions[0].project_id).toBe(gitBasedProjectID) // Uses directory, not stale JSON + expect(sessions[0].project_id).toBe(ProjectID.make(gitBasedProjectID)) // Uses directory, not stale JSON }) test("uses filename for session id when JSON has different value", async () => { @@ -458,7 +459,7 @@ describe("JSON to SQLite migration", () => { const sessions = db.select().from(SessionTable).all() expect(sessions.length).toBe(1) expect(sessions[0].id).toBe("ses_from_filename") // Uses filename, not JSON id - expect(sessions[0].project_id).toBe("proj_test123abc") + expect(sessions[0].project_id).toBe(ProjectID.make("proj_test123abc")) }) test("is idempotent (running twice doesn't duplicate)", async () => { @@ -643,7 +644,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const projects = db.select().from(ProjectTable).all() expect(projects.length).toBe(1) - expect(projects[0].id).toBe("proj_test123abc") + expect(projects[0].id).toBe(ProjectID.make("proj_test123abc")) }) test("skips invalid todo entries while preserving source positions", async () => { From 387ab78bf69a130b87e8d428e796c1663ba6e745 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:01:56 -0500 Subject: [PATCH 087/200] chore: fix test --- .../app/e2e/session/session-review.spec.ts | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/app/e2e/session/session-review.spec.ts b/packages/app/e2e/session/session-review.spec.ts index 4198c733c0..89d04e67d9 100644 --- a/packages/app/e2e/session/session-review.spec.ts +++ b/packages/app/e2e/session/session-review.spec.ts @@ -101,12 +101,31 @@ async function waitMark(page: Parameters<typeof test>[0]["page"], file: string, ) } +async function spot(page: Parameters<typeof test>[0]["page"], file: string) { + return page.evaluate((file) => { + const view = document.querySelector('[data-slot="session-review-scroll"] .scroll-view__viewport') + if (!(view instanceof HTMLElement)) return null + + const row = Array.from(document.querySelectorAll("h3")).find( + (node) => node instanceof HTMLElement && node.textContent?.includes(file), + ) + if (!(row instanceof HTMLElement)) return null + + const a = row.getBoundingClientRect() + const b = view.getBoundingClientRect() + return { + top: a.top - b.top, + y: view.scrollTop, + } + }, file) +} + test("review keeps scroll position after a live diff update", async ({ page, withProject }) => { test.setTimeout(180_000) const tag = `review-${Date.now()}` const list = files(tag) - const hit = list[list.length - 2]! + const hit = list[list.length - 4]! const next = `${tag}-live` await page.setViewportSize({ width: 1600, height: 1000 }) @@ -160,8 +179,9 @@ test("review keeps scroll position after a live diff update", async ({ page, wit await expect(row).toBeVisible() await row.evaluate((el) => el.scrollIntoView({ block: "center" })) - await expect.poll(() => view.evaluate((el) => el.scrollTop)).toBeGreaterThan(200) - const prev = await view.evaluate((el) => el.scrollTop) + await expect.poll(async () => (await spot(page, hit.file))?.y ?? 0).toBeGreaterThan(200) + const prev = await spot(page, hit.file) + if (!prev) throw new Error(`missing review row for ${hit.file}`) await patch(sdk, session.id, edit(hit.file, hit.mark, next)) @@ -179,8 +199,15 @@ test("review keeps scroll position after a live diff update", async ({ page, wit await waitMark(page, hit.file, next) await expect - .poll(async () => Math.abs((await view.evaluate((el) => el.scrollTop)) - prev), { timeout: 60_000 }) - .toBeLessThanOrEqual(16) + .poll( + async () => { + const next = await spot(page, hit.file) + if (!next) return Number.POSITIVE_INFINITY + return Math.max(Math.abs(next.top - prev.top), Math.abs(next.y - prev.y)) + }, + { timeout: 60_000 }, + ) + .toBeLessThanOrEqual(32) }) }) }) From f96e2d4222d70849ef8d4992565dc94e64832243 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:03:15 -0500 Subject: [PATCH 088/200] tweak: adjust skill presentation to be a little less token heavy (#17098) --- packages/opencode/src/session/system.ts | 4 ++- packages/opencode/src/skill/skill.ts | 30 ++++++++++++++--------- packages/opencode/src/tool/skill.ts | 2 +- packages/opencode/test/tool/skill.test.ts | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 0f0f6b51b3..a4c4684ffe 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -64,7 +64,9 @@ export namespace SystemPrompt { return [ "Skills provide specialized instructions and workflows for specific tasks.", "Use the skill tool to load a skill when a task matches its description.", - list.length === 0 ? "No skills are currently available." : "\n" + Skill.fmt(list), + // the agents seem to ingest the information about skills a bit better if we present a more verbose + // version of them here and a less verbose version in tool description, rather than vice versa. + Skill.fmt(list, { verbose: true }), ].join("\n") } } diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 09cc787c80..fa984b3e11 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -196,17 +196,23 @@ export namespace Skill { return list.filter((skill) => PermissionNext.evaluate("skill", skill.name, agent.permission).action !== "deny") } - export function fmt(list: Info[]) { - return [ - "<available_skills>", - ...list.flatMap((skill) => [ - ` <skill>`, - ` <name>${skill.name}</name>`, - ` <description>${skill.description}</description>`, - ` <location>${pathToFileURL(skill.location).href}</location>`, - ` </skill>`, - ]), - "</available_skills>", - ].join("\n") + export function fmt(list: Info[], opts: { verbose: boolean }) { + if (list.length === 0) { + return "No skills are currently available." + } + if (opts.verbose) { + return [ + "<available_skills>", + ...list.flatMap((skill) => [ + ` <skill>`, + ` <name>${skill.name}</name>`, + ` <description>${skill.description}</description>`, + ` <location>${pathToFileURL(skill.location).href}</location>`, + ` </skill>`, + ]), + "</available_skills>", + ].join("\n") + } + return ["## Available Skills", ...list.flatMap((skill) => `- **${skill.name}**: ${skill.description}`)].join("\n") } } diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts index 6d2a48b0ed..17016b06f8 100644 --- a/packages/opencode/src/tool/skill.ts +++ b/packages/opencode/src/tool/skill.ts @@ -24,7 +24,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => { "The following skills provide specialized sets of instructions for particular tasks", "Invoke this tool to load a skill when a task matches one of the available skills listed below:", "", - Skill.fmt(list), + Skill.fmt(list, { verbose: false }), ].join("\n") const examples = list diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index d5057ba9e7..c47259f155 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -45,7 +45,7 @@ description: Skill for tool tests. fn: async () => { const tool = await SkillTool.init() const skillPath = path.join(tmp.path, ".opencode", "skill", "tool-skill", "SKILL.md") - expect(tool.description).toContain(`<location>${pathToFileURL(skillPath).href}</location>`) + expect(tool.description).toContain(`**tool-skill**: Skill for tool tests.`) }, }) } finally { From a1cda29012e9521b1453950b5490fe18db1db4d5 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:10:58 -0500 Subject: [PATCH 089/200] chore: fix test --- packages/app/e2e/session/session-review.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/app/e2e/session/session-review.spec.ts b/packages/app/e2e/session/session-review.spec.ts index 89d04e67d9..fbca8d33ad 100644 --- a/packages/app/e2e/session/session-review.spec.ts +++ b/packages/app/e2e/session/session-review.spec.ts @@ -85,7 +85,10 @@ async function expand(page: Parameters<typeof test>[0]["page"]) { async function waitMark(page: Parameters<typeof test>[0]["page"], file: string, mark: string) { await page.waitForFunction( ({ file, mark }) => { - const head = Array.from(document.querySelectorAll("h3")).find( + const view = document.querySelector('[data-slot="session-review-scroll"] .scroll-view__viewport') + if (!(view instanceof HTMLElement)) return false + + const head = Array.from(view.querySelectorAll("h3")).find( (node) => node instanceof HTMLElement && node.textContent?.includes(file), ) if (!(head instanceof HTMLElement)) return false @@ -106,7 +109,7 @@ async function spot(page: Parameters<typeof test>[0]["page"], file: string) { const view = document.querySelector('[data-slot="session-review-scroll"] .scroll-view__viewport') if (!(view instanceof HTMLElement)) return null - const row = Array.from(document.querySelectorAll("h3")).find( + const row = Array.from(view.querySelectorAll("h3")).find( (node) => node instanceof HTMLElement && node.textContent?.includes(file), ) if (!(row instanceof HTMLElement)) return null From 440405dbddd62c57d983fdcf09d9ed55cc6711a8 Mon Sep 17 00:00:00 2001 From: Noam Bressler <noamzbr@gmail.com> Date: Wed, 11 Mar 2026 23:18:40 +0200 Subject: [PATCH 090/200] fix: re-enable snapshot in acp (#14918) --- packages/opencode/src/snapshot/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 848f2694e0..72252b7b4c 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -29,7 +29,7 @@ export namespace Snapshot { } export async function cleanup() { - if (Instance.project.vcs !== "git" || Flag.OPENCODE_CLIENT === "acp") return + if (Instance.project.vcs !== "git") return const cfg = await Config.get() if (cfg.snapshot === false) return const git = gitdir() @@ -54,7 +54,7 @@ export namespace Snapshot { } export async function track() { - if (Instance.project.vcs !== "git" || Flag.OPENCODE_CLIENT === "acp") return + if (Instance.project.vcs !== "git") return const cfg = await Config.get() if (cfg.snapshot === false) return const git = gitdir() From 58f45ae22b0faeba4a98910b0d3b42146846462b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:20:56 -0500 Subject: [PATCH 091/200] chore: skip test --- packages/app/e2e/session/session-review.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/e2e/session/session-review.spec.ts b/packages/app/e2e/session/session-review.spec.ts index fbca8d33ad..28c85edb0b 100644 --- a/packages/app/e2e/session/session-review.spec.ts +++ b/packages/app/e2e/session/session-review.spec.ts @@ -124,6 +124,7 @@ async function spot(page: Parameters<typeof test>[0]["page"], file: string) { } test("review keeps scroll position after a live diff update", async ({ page, withProject }) => { + test.skip(Boolean(process.env.CI), "Flaky in CI for now.") test.setTimeout(180_000) const tag = `review-${Date.now()}` From fbd9b7cf4fb27433a066a10a197c7edb420336d1 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:34:48 -0500 Subject: [PATCH 092/200] feat(app): restore to message and fork session (#17092) --- packages/app/src/i18n/en.ts | 5 + packages/app/src/pages/session.tsx | 116 ++++++++++++++++++ .../composer/session-composer-region.tsx | 40 +++++- .../session/composer/session-revert-dock.tsx | 92 ++++++++++++++ .../src/pages/session/message-timeline.tsx | 7 ++ packages/ui/src/components/icon.tsx | 2 + packages/ui/src/components/message-part.tsx | 59 +++++++++ packages/ui/src/components/session-turn.tsx | 11 +- packages/ui/src/i18n/en.ts | 2 + 9 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 packages/app/src/pages/session/composer/session-revert-dock.tsx diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 97a572f1cf..c87e7cb9db 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -530,6 +530,11 @@ export const dict = { "session.todo.title": "Todos", "session.todo.collapse": "Collapse", "session.todo.expand": "Expand", + "session.revertDock.summary.one": "{{count}} rolled back message", + "session.revertDock.summary.other": "{{count}} rolled back messages", + "session.revertDock.collapse": "Collapse rolled back messages", + "session.revertDock.expand": "Expand rolled back messages", + "session.revertDock.restore": "Restore message", "session.new.title": "Build anything", "session.new.worktree.main": "Main branch", diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index a5c7bf90b3..1b62b94294 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -43,6 +43,7 @@ import { SessionSidePanel } from "@/pages/session/session-side-panel" import { TerminalPanel } from "@/pages/session/terminal-panel" import { useSessionCommands } from "@/pages/session/use-session-commands" import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll" +import { extractPromptFromParts } from "@/utils/prompt" import { same } from "@/utils/same" import { formatServerError } from "@/utils/server-errors" @@ -286,6 +287,7 @@ export default function Page() { const [ui, setUi] = createStore({ git: false, pendingMessage: undefined as string | undefined, + restoring: undefined as string | undefined, reviewSnap: false, scrollGesture: 0, scroll: { @@ -1179,6 +1181,110 @@ export default function Page() { scroller: () => scroller, }) + const draft = (id: string) => + extractPromptFromParts(sync.data.part[id] ?? [], { + directory: sdk.directory, + attachmentName: language.t("common.attachment"), + }) + + const line = (id: string) => { + const text = draft(id) + .map((part) => (part.type === "image" ? `[image:${part.filename}]` : part.content)) + .join("") + .replace(/\s+/g, " ") + .trim() + if (text) return text + return `[${language.t("common.attachment")}]` + } + + const fail = (err: unknown) => { + showToast({ + variant: "error", + title: language.t("common.requestFailed"), + description: formatServerError(err, language.t), + }) + } + + const busy = (sessionID: string) => { + if (sync.data.session_status[sessionID]?.type !== "idle") return true + return (sync.data.message[sessionID] ?? []).some( + (item) => item.role === "assistant" && typeof item.time.completed !== "number", + ) + } + + const halt = (sessionID: string) => + busy(sessionID) ? sdk.client.session.abort({ sessionID }).catch(() => {}) : Promise.resolve() + + const fork = (input: { sessionID: string; messageID: string }) => { + const value = draft(input.messageID) + return sdk.client.session + .fork(input) + .then((result) => { + const next = result.data + if (!next) { + showToast({ + variant: "error", + title: language.t("common.requestFailed"), + }) + return + } + navigate(`/${base64Encode(sdk.directory)}/session/${next.id}`) + requestAnimationFrame(() => { + prompt.set(value) + }) + }) + .catch(fail) + } + + const revert = (input: { sessionID: string; messageID: string }) => { + const value = draft(input.messageID) + return halt(input.sessionID) + .then(() => sdk.client.session.revert(input)) + .then(() => { + prompt.set(value) + }) + .catch(fail) + } + + const restore = (id: string) => { + const sessionID = params.id + if (!sessionID || ui.restoring) return + + const next = userMessages().find((item) => item.id > id) + setUi("restoring", id) + + const task = !next + ? halt(sessionID) + .then(() => sdk.client.session.unrevert({ sessionID })) + .then(() => { + prompt.reset() + }) + : halt(sessionID) + .then(() => + sdk.client.session.revert({ + sessionID, + messageID: next.id, + }), + ) + .then(() => { + prompt.set(draft(next.id)) + }) + + return task.catch(fail).finally(() => { + setUi("restoring", (value) => (value === id ? undefined : value)) + }) + } + + const rolled = createMemo(() => { + const id = revertMessageID() + if (!id) return [] + return userMessages() + .filter((item) => item.id >= id) + .map((item) => ({ id: item.id, text: line(item.id) })) + }) + + const actions = { fork, revert } + createResizeObserver( () => promptDock, ({ height }) => { @@ -1268,6 +1374,7 @@ export default function Page() { loadingClass: "px-4 py-4 text-text-weak", emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6", })} + actions={actions} scroll={ui.scroll} onResumeScroll={resumeScroll} setScrollRef={setScrollRef} @@ -1333,6 +1440,15 @@ export default function Page() { resumeScroll() }} onResponseSubmit={resumeScroll} + revert={ + rolled().length > 0 + ? { + items: rolled(), + restoring: ui.restoring, + onRestore: restore, + } + : undefined + } setPromptDockRef={(el) => { promptDock = el }} diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx index 93ea3d465c..08746b51a5 100644 --- a/packages/app/src/pages/session/composer/session-composer-region.tsx +++ b/packages/app/src/pages/session/composer/session-composer-region.tsx @@ -8,6 +8,7 @@ import { usePrompt } from "@/context/prompt" import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff" import { SessionPermissionDock } from "@/pages/session/composer/session-permission-dock" import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock" +import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock" import type { SessionComposerState } from "@/pages/session/composer/session-composer-state" import { SessionTodoDock } from "@/pages/session/composer/session-todo-dock" @@ -20,6 +21,11 @@ export function SessionComposerRegion(props: { onNewSessionWorktreeReset: () => void onSubmit: () => void onResponseSubmit: () => void + revert?: { + items: { id: string; text: string }[] + restoring?: string + onRestore: (id: string) => void + } setPromptDockRef: (el: HTMLDivElement) => void visualDuration?: number bounce?: number @@ -116,6 +122,8 @@ export function SessionComposerRegion(props: { const value = createMemo(() => Math.max(0, Math.min(1, progress()))) const [height, setHeight] = createSignal(320) const dock = createMemo(() => (gate.ready && props.state.dock()) || value() > 0.001) + const rolled = createMemo(() => (props.revert?.items.length ? props.revert : undefined)) + const lift = createMemo(() => (rolled() ? 18 : 36 * value())) const full = createMemo(() => Math.max(78, height())) const [contentRef, setContentRef] = createSignal<HTMLDivElement>() @@ -170,9 +178,22 @@ export function SessionComposerRegion(props: { <Show when={prompt.ready()} fallback={ - <div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none"> - {handoffPrompt() || language.t("prompt.loading")} - </div> + <> + <Show when={rolled()} keyed> + {(revert) => ( + <div class="pb-2"> + <SessionRevertDock + items={revert.items} + restoring={revert.restoring} + onRestore={revert.onRestore} + /> + </div> + )} + </Show> + <div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none"> + {handoffPrompt() || language.t("prompt.loading")} + </div> + </> } > <Show when={dock()}> @@ -209,12 +230,23 @@ export function SessionComposerRegion(props: { </div> </div> </Show> + <Show when={rolled()} keyed> + {(revert) => ( + <div + style={{ + "margin-top": `${-36 * value()}px`, + }} + > + <SessionRevertDock items={revert.items} restoring={revert.restoring} onRestore={revert.onRestore} /> + </div> + )} + </Show> <div classList={{ "relative z-10": true, }} style={{ - "margin-top": `${-36 * value()}px`, + "margin-top": `${-lift()}px`, }} > <PromptInput diff --git a/packages/app/src/pages/session/composer/session-revert-dock.tsx b/packages/app/src/pages/session/composer/session-revert-dock.tsx new file mode 100644 index 0000000000..ce7644b67c --- /dev/null +++ b/packages/app/src/pages/session/composer/session-revert-dock.tsx @@ -0,0 +1,92 @@ +import { For, Show, createMemo } from "solid-js" +import { createStore } from "solid-js/store" +import { Button } from "@opencode-ai/ui/button" +import { DockTray } from "@opencode-ai/ui/dock-surface" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { useLanguage } from "@/context/language" + +export function SessionRevertDock(props: { + items: { id: string; text: string }[] + restoring?: string + onRestore: (id: string) => void +}) { + const language = useLanguage() + const [store, setStore] = createStore({ + collapsed: false, + }) + + const toggle = () => setStore("collapsed", (value) => !value) + const total = createMemo(() => props.items.length) + const label = createMemo(() => + language.t(total() === 1 ? "session.revertDock.summary.one" : "session.revertDock.summary.other", { + count: total(), + }), + ) + const preview = createMemo(() => props.items[0]?.text ?? "") + + return ( + <DockTray data-component="session-revert-dock"> + <div + class="pl-3 pr-2 py-2 flex items-center gap-2" + role="button" + tabIndex={0} + onClick={toggle} + onKeyDown={(event) => { + if (event.key !== "Enter" && event.key !== " ") return + event.preventDefault() + toggle() + }} + > + <span class="shrink-0 text-14-regular text-text-strong cursor-default">{label()}</span> + <Show when={store.collapsed && preview()}> + <span class="min-w-0 flex-1 truncate text-14-regular text-text-base cursor-default">{preview()}</span> + </Show> + <div class="ml-auto shrink-0"> + <IconButton + data-collapsed={store.collapsed ? "true" : "false"} + icon="chevron-down" + size="normal" + variant="ghost" + style={{ transform: `rotate(${store.collapsed ? 180 : 0}deg)` }} + onMouseDown={(event) => { + event.preventDefault() + event.stopPropagation() + }} + onClick={(event) => { + event.stopPropagation() + toggle() + }} + aria-label={ + store.collapsed ? language.t("session.revertDock.expand") : language.t("session.revertDock.collapse") + } + /> + </div> + </div> + + <Show when={store.collapsed}> + <div class="h-5" aria-hidden="true" /> + </Show> + + <Show when={!store.collapsed}> + <div class="px-3 pb-11 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar"> + <For each={props.items}> + {(item) => ( + <div class="flex items-center gap-2 min-w-0 rounded-[10px] border border-border-weak-base bg-background-stronger px-2.5 py-2"> + <span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span> + <Button + size="small" + variant="secondary" + class="shrink-0" + disabled={!!props.restoring} + onClick={() => props.onRestore(item.id)} + > + {language.t("session.revertDock.restore")} + </Button> + </div> + )} + </For> + </div> + </Show> + </DockTray> + ) +} diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 223539092f..cb9c61918a 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -36,6 +36,11 @@ type MessageComment = { const emptyMessages: MessageType[] = [] const idle = { type: "idle" as const } +type UserActions = { + fork?: (input: { sessionID: string; messageID: string }) => Promise<void> | void + revert?: (input: { sessionID: string; messageID: string }) => Promise<void> | void +} + const messageComments = (parts: Part[]): MessageComment[] => parts.flatMap((part) => { if (part.type !== "text" || !(part as TextPart).synthetic) return [] @@ -186,6 +191,7 @@ function createTimelineStaging(input: TimelineStageInput) { export function MessageTimeline(props: { mobileChanges: boolean mobileFallback: JSX.Element + actions?: UserActions scroll: { overflow: boolean; bottom: boolean } onResumeScroll: () => void setScrollRef: (el: HTMLDivElement | undefined) => void @@ -805,6 +811,7 @@ export function MessageTimeline(props: { <SessionTurn sessionID={sessionID() ?? ""} messageID={messageID} + actions={props.actions} active={active()} queued={queued()} status={active() ? sessionStatus() : undefined} diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 6486da8519..7f4598f297 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -9,6 +9,7 @@ const icons = { "bubble-5": `<path d="M18.3327 9.99935C18.3327 5.57227 15.0919 2.91602 9.99935 2.91602C4.90676 2.91602 1.66602 5.57227 1.66602 9.99935C1.66602 11.1487 2.45505 13.1006 2.57637 13.3939C2.58707 13.4197 2.59766 13.4434 2.60729 13.4697C2.69121 13.6987 3.04209 14.9354 1.66602 16.7674C3.51787 17.6528 5.48453 16.1973 5.48453 16.1973C6.84518 16.9193 8.46417 17.0827 9.99935 17.0827C15.0919 17.0827 18.3327 14.4264 18.3327 9.99935Z" stroke="currentColor" stroke-linecap="square"/>`, prompt: `<path d="M14.5841 12.0807H17.9193V2.91406H5.6276V6.2474M14.5859 6.2474H2.08594V15.4141H5.0026V17.4974L8.7526 15.4141H14.5859V6.2474Z" stroke="currentColor" stroke-linecap="square"/>`, brain: `<path d="M13.332 8.7487C11.4911 8.7487 9.9987 7.25631 9.9987 5.41536M6.66536 11.2487C8.50631 11.2487 9.9987 12.7411 9.9987 14.582M9.9987 2.78209L9.9987 17.0658M16.004 15.0475C17.1255 14.5876 17.9154 13.4849 17.9154 12.1978C17.9154 11.3363 17.5615 10.5575 16.9913 9.9987C17.5615 9.43991 17.9154 8.66108 17.9154 7.79962C17.9154 6.21199 16.7136 4.90504 15.1702 4.73878C14.7858 3.21216 13.4039 2.08203 11.758 2.08203C11.1171 2.08203 10.5162 2.25337 9.9987 2.55275C9.48117 2.25337 8.88032 2.08203 8.23944 2.08203C6.59353 2.08203 5.21157 3.21216 4.82722 4.73878C3.28377 4.90504 2.08203 6.21199 2.08203 7.79962C2.08203 8.66108 2.43585 9.43991 3.00609 9.9987C2.43585 10.5575 2.08203 11.3363 2.08203 12.1978C2.08203 13.4849 2.87191 14.5876 3.99339 15.0475C4.46688 16.7033 5.9917 17.9154 7.79962 17.9154C8.61335 17.9154 9.36972 17.6698 9.9987 17.2488C10.6277 17.6698 11.384 17.9154 12.1978 17.9154C14.0057 17.9154 15.5305 16.7033 16.004 15.0475Z" stroke="currentColor"/>`, + fork: `<path d="M2.91602 7.91406L2.91602 2.91406H7.91602M12.0827 2.91406H17.0827L17.0827 7.91406M9.99935 9.9974L9.99935 17.0807M9.99935 9.9974L3.33268 3.33073M9.99935 9.9974L16.666 3.33073" stroke="currentColor" stroke-linecap="square"/>`, "bullet-list": `<path d="M9.58329 13.7497H17.0833M9.58329 6.24967H17.0833M6.24996 6.24967C6.24996 7.17015 5.50377 7.91634 4.58329 7.91634C3.66282 7.91634 2.91663 7.17015 2.91663 6.24967C2.91663 5.3292 3.66282 4.58301 4.58329 4.58301C5.50377 4.58301 6.24996 5.3292 6.24996 6.24967ZM6.24996 13.7497C6.24996 14.6701 5.50377 15.4163 4.58329 15.4163C3.66282 15.4163 2.91663 14.6701 2.91663 13.7497C2.91663 12.8292 3.66282 12.083 4.58329 12.083C5.50377 12.083 6.24996 12.8292 6.24996 13.7497Z" stroke="currentColor" stroke-linecap="square"/>`, "check-small": `<path d="M6.5 11.4412L8.97059 13.5L13.5 6.5" stroke="currentColor" stroke-linecap="square"/>`, "chevron-down": `<path d="M6.6665 8.33325L9.99984 11.6666L13.3332 8.33325" stroke="currentColor" stroke-linecap="square"/>`, @@ -80,6 +81,7 @@ const icons = { selector: `<path d="M6.66626 12.5033L9.99959 15.8366L13.3329 12.5033M6.66626 7.50326L9.99959 4.16992L13.3329 7.50326" stroke="currentColor" stroke-linecap="square"/>`, "arrow-down-to-line": `<path d="M15.2083 11.6667L10 16.875L4.79167 11.6667M10 16.25V3.125" stroke="currentColor" stroke-width="1.25" stroke-linecap="square"/>`, warning: `<path d="M10 7.91667V11.6667M10 13.7417V13.75M10 2.5L1.875 16.25H18.125L10 2.5Z" stroke="currentColor" stroke-linecap="square"/>`, + reset: `<path d="M5.83333 4.16406L2.5 7.4974L5.83333 10.8307M3.33333 7.4974H17.9167V15.4141H10" stroke="currentColor" stroke-linecap="square"/>`, link: `<path d="M2.08334 12.0833L1.72979 11.7298L1.37624 12.0833L1.72979 12.4369L2.08334 12.0833ZM7.91668 17.9167L7.56312 18.2702L7.91668 18.6238L8.27023 18.2702L7.91668 17.9167ZM17.9167 7.91666L18.2702 8.27022L18.6238 7.91666L18.2702 7.56311L17.9167 7.91666ZM12.0833 2.08333L12.4369 1.72977L12.0833 1.37622L11.7298 1.72977L12.0833 2.08333ZM8.39646 5.06311L8.0429 5.41666L8.75001 6.12377L9.10356 5.77021L8.75001 5.41666L8.39646 5.06311ZM5.77023 9.10355L6.12378 8.74999L5.41668 8.04289L5.06312 8.39644L5.41668 8.74999L5.77023 9.10355ZM14.2298 10.8964L13.8762 11.25L14.5833 11.9571L14.9369 11.6035L14.5833 11.25L14.2298 10.8964ZM11.6036 14.9369L11.9571 14.5833L11.25 13.8762L10.8965 14.2298L11.25 14.5833L11.6036 14.9369ZM7.14646 12.1464L6.7929 12.5L7.50001 13.2071L7.85356 12.8535L7.50001 12.5L7.14646 12.1464ZM12.8536 7.85355L13.2071 7.49999L12.5 6.79289L12.1465 7.14644L12.5 7.49999L12.8536 7.85355ZM2.08334 12.0833L1.72979 12.4369L7.56312 18.2702L7.91668 17.9167L8.27023 17.5631L2.4369 11.7298L2.08334 12.0833ZM17.9167 7.91666L18.2702 7.56311L12.4369 1.72977L12.0833 2.08333L11.7298 2.43688L17.5631 8.27022L17.9167 7.91666ZM12.0833 2.08333L11.7298 1.72977L8.39646 5.06311L8.75001 5.41666L9.10356 5.77021L12.4369 2.43688L12.0833 2.08333ZM5.41668 8.74999L5.06312 8.39644L1.72979 11.7298L2.08334 12.0833L2.4369 12.4369L5.77023 9.10355L5.41668 8.74999ZM14.5833 11.25L14.9369 11.6035L18.2702 8.27022L17.9167 7.91666L17.5631 7.56311L14.2298 10.8964L14.5833 11.25ZM7.91668 17.9167L8.27023 18.2702L11.6036 14.9369L11.25 14.5833L10.8965 14.2298L7.56312 17.5631L7.91668 17.9167ZM7.50001 12.5L7.85356 12.8535L12.8536 7.85355L12.5 7.49999L12.1465 7.14644L7.14646 12.1464L7.50001 12.5Z" fill="currentColor"/>`, providers: `<path d="M10.0001 4.37562V2.875M13 4.37793V2.87793M7.00014 4.37793V2.875M10 17.1279V15.6279M13 17.1279V15.6279M7 17.1279V15.6279M15.625 13.0029H17.125M15.625 7.00293H17.125M15.625 10.0029H17.125M2.875 10.0029H4.375M2.875 13.0029H4.375M2.875 7.00293H4.375M4.375 4.37793H15.625V15.6279H4.375V4.37793ZM12.6241 10.0022C12.6241 11.4519 11.4488 12.6272 9.99908 12.6272C8.54934 12.6272 7.37408 11.4519 7.37408 10.0022C7.37408 8.55245 8.54934 7.3772 9.99908 7.3772C11.4488 7.3772 12.6241 8.55245 12.6241 10.0022Z" stroke="currentColor" stroke-linecap="square"/>`, models: `<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5 10C12.2917 10 10 12.2917 10 17.5C10 12.2917 7.70833 10 2.5 10C7.70833 10 10 7.70833 10 2.5C10 7.70833 12.2917 10 17.5 10Z" stroke="currentColor"/>`, diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 65b6229d6f..1a50ed48c0 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -129,12 +129,20 @@ function DiagnosticsDisplay(props: { diagnostics: Diagnostic[] }): JSX.Element { export interface MessageProps { message: MessageType parts: PartType[] + actions?: UserActions showAssistantCopyPartID?: string | null interrupted?: boolean queued?: boolean showReasoningSummaries?: boolean } +export type SessionAction = (input: { sessionID: string; messageID: string }) => Promise<void> | void + +export type UserActions = { + fork?: SessionAction + revert?: SessionAction +} + export interface MessagePartProps { part: PartType message: MessageType @@ -676,6 +684,7 @@ export function Message(props: MessageProps) { <UserMessageDisplay message={userMessage() as UserMessage} parts={props.parts} + actions={props.actions} interrupted={props.interrupted} queued={props.queued} /> @@ -872,6 +881,7 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) { export function UserMessageDisplay(props: { message: UserMessage parts: PartType[] + actions?: UserActions interrupted?: boolean queued?: boolean }) { @@ -879,6 +889,7 @@ export function UserMessageDisplay(props: { const dialog = useDialog() const i18n = useI18n() const [copied, setCopied] = createSignal(false) + const [busy, setBusy] = createSignal<"fork" | "revert" | undefined>() const textPart = createMemo( () => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined, @@ -945,6 +956,22 @@ export function UserMessageDisplay(props: { setTimeout(() => setCopied(false), 2000) } + const run = (kind: "fork" | "revert") => { + const act = kind === "fork" ? props.actions?.fork : props.actions?.revert + if (!act || busy()) return + setBusy(kind) + void Promise.resolve() + .then(() => + act({ + sessionID: props.message.sessionID, + messageID: props.message.id, + }), + ) + .finally(() => { + if (busy() === kind) setBusy(undefined) + }) + } + return ( <div data-component="user-message" data-interrupted={props.interrupted ? "" : undefined}> <Show when={attachments().length > 0}> @@ -1012,6 +1039,38 @@ export function UserMessageDisplay(props: { </Show> </span> </Show> + <Show when={props.actions?.fork}> + <Tooltip value={i18n.t("ui.message.forkMessage")} placement="top" gutter={4}> + <IconButton + icon="fork" + size="normal" + variant="ghost" + disabled={!!busy()} + onMouseDown={(e) => e.preventDefault()} + onClick={(event) => { + event.stopPropagation() + run("fork") + }} + aria-label={i18n.t("ui.message.forkMessage")} + /> + </Tooltip> + </Show> + <Show when={props.actions?.revert}> + <Tooltip value={i18n.t("ui.message.revertMessage")} placement="top" gutter={4}> + <IconButton + icon="reset" + size="normal" + variant="ghost" + disabled={!!busy()} + onMouseDown={(e) => e.preventDefault()} + onClick={(event) => { + event.stopPropagation() + run("revert") + }} + aria-label={i18n.t("ui.message.revertMessage")} + /> + </Tooltip> + </Show> <Tooltip value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")} placement="top" diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 3323a9fc66..fda02cab45 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -7,7 +7,7 @@ import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { Dynamic } from "solid-js/web" -import { AssistantParts, Message, Part, PART_MAPPING } from "./message-part" +import { AssistantParts, Message, Part, PART_MAPPING, type UserActions } from "./message-part" import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" @@ -141,6 +141,7 @@ export function SessionTurn( props: ParentProps<{ sessionID: string messageID: string + actions?: UserActions showReasoningSummaries?: boolean shellToolDefaultOpen?: boolean editToolDefaultOpen?: boolean @@ -395,7 +396,13 @@ export function SessionTurn( class={props.classes?.container} > <div data-slot="session-turn-message-content" aria-live="off"> - <Message message={message()!} parts={parts()} interrupted={interrupted()} queued={queued()} /> + <Message + message={message()!} + parts={parts()} + actions={props.actions} + interrupted={interrupted()} + queued={queued()} + /> </div> <Show when={compaction()}> <div data-slot="session-turn-compaction"> diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 7f4a4020ad..3551f889da 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -127,6 +127,8 @@ export const dict: Record<string, string> = { "ui.message.collapse": "Collapse message", "ui.message.copy": "Copy", "ui.message.copyMessage": "Copy message", + "ui.message.forkMessage": "Fork to new session", + "ui.message.revertMessage": "Reset to this point", "ui.message.copyResponse": "Copy response", "ui.message.copied": "Copied", "ui.message.interrupted": "Interrupted", From 604ab1bde11293e9d6fa13f3a08b3cdd2847f186 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Wed, 11 Mar 2026 17:41:49 -0400 Subject: [PATCH 093/200] core: restore plugin serverUrl getter so plugins can connect to local server --- packages/opencode/src/plugin/index.ts | 2 +- packages/opencode/src/server/server.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 1c129f6082..1b9d009aae 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -35,7 +35,7 @@ export namespace Plugin { worktree: Instance.worktree, directory: Instance.directory, get serverUrl(): URL { - throw new Error("Server URL is no longer supported in plugins") + return Server.url ?? new URL("http://localhost:4096") }, $: Bun.$, } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 3d435c8c99..0566547b73 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -585,6 +585,9 @@ export namespace Server { return result } + /** @deprecated do not use this dumb shit */ + export let url: URL + export function listen(opts: { port: number hostname: string @@ -592,6 +595,7 @@ export namespace Server { mdnsDomain?: string cors?: string[] }) { + url = new URL(`http://${opts.hostname}:${opts.port}`) const app = createApp(opts) const args = { hostname: opts.hostname, From f89696509e1d97599cc041e0d1034c2933374837 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 00:04:54 -0400 Subject: [PATCH 094/200] zen: update header --- packages/console/app/src/component/header.tsx | 16 ++++++---------- packages/console/app/src/routes/zen/index.tsx | 3 +-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/console/app/src/component/header.tsx b/packages/console/app/src/component/header.tsx index 24d5a897ce..1e129d5908 100644 --- a/packages/console/app/src/component/header.tsx +++ b/packages/console/app/src/component/header.tsx @@ -161,16 +161,12 @@ export function Header(props: { zen?: boolean; go?: boolean; hideGetStarted?: bo <li> <a href={language.route("/docs")}>{i18n.t("nav.docs")}</a> </li> - <Show when={!props.zen}> - <li> - <A href={language.route("/zen")}>{i18n.t("nav.zen")}</A> - </li> - </Show> - <Show when={!props.go}> - <li> - <A href={language.route("/go")}>{i18n.t("nav.go")}</A> - </li> - </Show> + <li> + <A href={language.route("/zen")}>{i18n.t("nav.zen")}</A> + </li> + <li> + <A href={language.route("/go")}>{i18n.t("nav.go")}</A> + </li> <li> <A href={language.route("/enterprise")}>{i18n.t("nav.enterprise")}</A> </li> diff --git a/packages/console/app/src/routes/zen/index.tsx b/packages/console/app/src/routes/zen/index.tsx index 5b5581b53b..62e8f5d379 100644 --- a/packages/console/app/src/routes/zen/index.tsx +++ b/packages/console/app/src/routes/zen/index.tsx @@ -24,8 +24,7 @@ import { LocaleLinks } from "~/component/locale-links" const checkLoggedIn = query(async () => { "use server" - const workspaceID = await getLastSeenWorkspaceID().catch(() => {}) - if (workspaceID) throw redirect(`/workspace/${workspaceID}`) + return await getLastSeenWorkspaceID().catch(() => {}) }, "checkLoggedIn.get") export default function Home() { From fac23a1afc6cfbb7767eef05bf6ea018837697ad Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 00:05:08 -0400 Subject: [PATCH 095/200] zen: update usage graph on landing page --- packages/console/app/src/routes/go/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index 842256ca3d..dcd909ab40 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -62,7 +62,7 @@ function LimitsGraph(props: { href: string }) { const rmax = Math.max(1, ...models.map((m) => ratio(m.req))) const log = (n: number) => Math.log10(Math.max(n, 1)) const base = 24 - const p = 2.2 + const p = 1.8 const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base) const start = (x(1) / w) * 100 From ed3bb3ea8f18a3d2818eb17d77e2caad40eb1dee Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 00:39:56 -0400 Subject: [PATCH 096/200] zen: add usage section --- packages/console/app/src/i18n/ar.ts | 1 + packages/console/app/src/i18n/br.ts | 1 + packages/console/app/src/i18n/da.ts | 1 + packages/console/app/src/i18n/de.ts | 1 + packages/console/app/src/i18n/en.ts | 1 + packages/console/app/src/i18n/es.ts | 1 + packages/console/app/src/i18n/fr.ts | 1 + packages/console/app/src/i18n/it.ts | 1 + packages/console/app/src/i18n/ja.ts | 1 + packages/console/app/src/i18n/ko.ts | 1 + packages/console/app/src/i18n/no.ts | 1 + packages/console/app/src/i18n/pl.ts | 1 + packages/console/app/src/i18n/ru.ts | 1 + packages/console/app/src/i18n/th.ts | 1 + packages/console/app/src/i18n/tr.ts | 1 + packages/console/app/src/i18n/zh.ts | 1 + packages/console/app/src/i18n/zht.ts | 1 + .../console/app/src/routes/workspace/[id].tsx | 6 ++++++ .../app/src/routes/workspace/[id]/index.tsx | 6 ------ .../[id]/{ => usage}/graph-section.module.css | 0 .../[id]/{ => usage}/graph-section.tsx | 0 .../src/routes/workspace/[id]/usage/index.tsx | 21 +++++++++++++++++++ .../[id]/{ => usage}/usage-section.module.css | 0 .../[id]/{ => usage}/usage-section.tsx | 2 +- 24 files changed, 45 insertions(+), 7 deletions(-) rename packages/console/app/src/routes/workspace/[id]/{ => usage}/graph-section.module.css (100%) rename packages/console/app/src/routes/workspace/[id]/{ => usage}/graph-section.tsx (100%) create mode 100644 packages/console/app/src/routes/workspace/[id]/usage/index.tsx rename packages/console/app/src/routes/workspace/[id]/{ => usage}/usage-section.module.css (100%) rename packages/console/app/src/routes/workspace/[id]/{ => usage}/usage-section.tsx (99%) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 86d51226a6..a175309208 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -411,6 +411,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "سيتم خصم المبلغ من بطاقتك عند تفعيل اشتراكك", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "الاستخدام", "workspace.nav.apiKeys": "مفاتيح API", "workspace.nav.members": "الأعضاء", "workspace.nav.billing": "الفوترة", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index f14a69c85c..f395db9f49 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -418,6 +418,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Seu cartão será cobrado quando sua assinatura for ativada", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Uso", "workspace.nav.apiKeys": "Chaves de API", "workspace.nav.members": "Membros", "workspace.nav.billing": "Faturamento", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 775f029fba..6e2af12af4 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -414,6 +414,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Dit kort vil blive debiteret, når dit abonnement er aktiveret", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Brug", "workspace.nav.apiKeys": "API-nøgler", "workspace.nav.members": "Medlemmer", "workspace.nav.billing": "Fakturering", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 2d9be14ff7..87094a28f1 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -417,6 +417,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Deine Karte wird belastet, sobald dein Abonnement aktiviert ist", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Nutzung", "workspace.nav.apiKeys": "API Keys", "workspace.nav.members": "Mitglieder", "workspace.nav.billing": "Abrechnung", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 02d6126c4a..1f1773dd41 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -411,6 +411,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Your card will be charged when your subscription is activated", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Usage", "workspace.nav.apiKeys": "API Keys", "workspace.nav.members": "Members", "workspace.nav.billing": "Billing", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 25b8d37d7c..19a9842d56 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -419,6 +419,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Tu tarjeta se cargará cuando tu suscripción se active", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Uso", "workspace.nav.apiKeys": "Claves API", "workspace.nav.members": "Miembros", "workspace.nav.billing": "Facturación", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index ddf33c0ec2..cf6d5e95b8 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -419,6 +419,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Votre carte sera débitée lorsque votre abonnement sera activé", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Utilisation", "workspace.nav.apiKeys": "Clés API", "workspace.nav.members": "Membres", "workspace.nav.billing": "Facturation", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 770efde453..b967f078c7 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -417,6 +417,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "La tua carta verrà addebitata quando il tuo abbonamento sarà attivato", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Utilizzo", "workspace.nav.apiKeys": "Chiavi API", "workspace.nav.members": "Membri", "workspace.nav.billing": "Fatturazione", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index f2786ba8d8..759d5e7c72 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -416,6 +416,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "サブスクリプションが有効化された時点でカードに請求されます", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "利用", "workspace.nav.apiKeys": "APIキー", "workspace.nav.members": "メンバー", "workspace.nav.billing": "請求", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index 169b56c0a3..9a5237f332 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -410,6 +410,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "구독이 활성화되면 카드에 청구됩니다", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "사용량", "workspace.nav.apiKeys": "API 키", "workspace.nav.members": "멤버", "workspace.nav.billing": "결제", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 0b6e76e0c3..fa44e535eb 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -415,6 +415,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Kortet ditt vil bli belastet når abonnementet aktiveres", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Bruk", "workspace.nav.apiKeys": "API-nøkler", "workspace.nav.members": "Medlemmer", "workspace.nav.billing": "Fakturering", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index b46280ae15..7c7300ab95 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -416,6 +416,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Twoja karta zostanie obciążona po aktywacji subskrypcji", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Użycie", "workspace.nav.apiKeys": "Klucze API", "workspace.nav.members": "Członkowie", "workspace.nav.billing": "Rozliczenia", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 801c8fc7d4..77e36bc992 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -421,6 +421,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "С вашей карты будет списана оплата при активации подписки", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Использование", "workspace.nav.apiKeys": "API Ключи", "workspace.nav.members": "Участники", "workspace.nav.billing": "Оплата", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index d9d7d03d1f..9d1e92fa8d 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -413,6 +413,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "บัตรของคุณจะถูกเรียกเก็บเงินเมื่อการสมัครสมาชิกของคุณถูกเปิดใช้งาน", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "การใช้งาน", "workspace.nav.apiKeys": "API Keys", "workspace.nav.members": "สมาชิก", "workspace.nav.billing": "การเรียกเก็บเงิน", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index e28afe2b06..84ad9a7e38 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -418,6 +418,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Aboneliğiniz aktive edildiğinde kartınızdan ödeme alınacaktır", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "Kullanım", "workspace.nav.apiKeys": "API Anahtarları", "workspace.nav.members": "Üyeler", "workspace.nav.billing": "Faturalandırma", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index e12fe77494..a3527ae3e0 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -396,6 +396,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "您的卡将在订阅激活时扣费", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "使用量", "workspace.nav.apiKeys": "API 密钥", "workspace.nav.members": "成员", "workspace.nav.billing": "计费", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index b45f871566..1167d5a6c7 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -397,6 +397,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "你的卡片將在訂閱啟用時扣款", "workspace.nav.zen": "Zen", + "workspace.nav.usage": "使用量", "workspace.nav.apiKeys": "API 金鑰", "workspace.nav.members": "成員", "workspace.nav.billing": "帳務", diff --git a/packages/console/app/src/routes/workspace/[id].tsx b/packages/console/app/src/routes/workspace/[id].tsx index fde074e5e1..a36368fd0f 100644 --- a/packages/console/app/src/routes/workspace/[id].tsx +++ b/packages/console/app/src/routes/workspace/[id].tsx @@ -19,6 +19,9 @@ export default function WorkspaceLayout(props: RouteSectionProps) { <A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button> {i18n.t("workspace.nav.zen")} </A> + <A href={`/workspace/${params.id}/usage`} activeClass="active" data-nav-button> + {i18n.t("workspace.nav.usage")} + </A> <A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button> {i18n.t("workspace.nav.apiKeys")} </A> @@ -41,6 +44,9 @@ export default function WorkspaceLayout(props: RouteSectionProps) { <A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button> {i18n.t("workspace.nav.zen")} </A> + <A href={`/workspace/${params.id}/usage`} activeClass="active" data-nav-button> + {i18n.t("workspace.nav.usage")} + </A> <A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button> {i18n.t("workspace.nav.apiKeys")} </A> diff --git a/packages/console/app/src/routes/workspace/[id]/index.tsx b/packages/console/app/src/routes/workspace/[id]/index.tsx index c91cfd2bcc..ca3c2bc0ca 100644 --- a/packages/console/app/src/routes/workspace/[id]/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/index.tsx @@ -2,10 +2,8 @@ import { Match, Show, Switch, createMemo } from "solid-js" import { createStore } from "solid-js/store" import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router" import { NewUserSection } from "./new-user-section" -import { UsageSection } from "./usage-section" import { ModelSection } from "./model-section" import { ProviderSection } from "./provider-section" -import { GraphSection } from "./graph-section" import { IconLogo } from "~/component/icon" import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common" import { useI18n } from "~/context/i18n" @@ -73,14 +71,10 @@ export default function () { <div data-slot="sections"> <NewUserSection /> - <Show when={userInfo()?.isAdmin}> - <GraphSection /> - </Show> <ModelSection /> <Show when={userInfo()?.isAdmin}> <ProviderSection /> </Show> - <UsageSection /> </div> </div> ) diff --git a/packages/console/app/src/routes/workspace/[id]/graph-section.module.css b/packages/console/app/src/routes/workspace/[id]/usage/graph-section.module.css similarity index 100% rename from packages/console/app/src/routes/workspace/[id]/graph-section.module.css rename to packages/console/app/src/routes/workspace/[id]/usage/graph-section.module.css diff --git a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx b/packages/console/app/src/routes/workspace/[id]/usage/graph-section.tsx similarity index 100% rename from packages/console/app/src/routes/workspace/[id]/graph-section.tsx rename to packages/console/app/src/routes/workspace/[id]/usage/graph-section.tsx diff --git a/packages/console/app/src/routes/workspace/[id]/usage/index.tsx b/packages/console/app/src/routes/workspace/[id]/usage/index.tsx new file mode 100644 index 0000000000..3a9c8db296 --- /dev/null +++ b/packages/console/app/src/routes/workspace/[id]/usage/index.tsx @@ -0,0 +1,21 @@ +import { Show } from "solid-js" +import { createAsync, useParams } from "@solidjs/router" +import { GraphSection } from "./graph-section" +import { UsageSection } from "./usage-section" +import { querySessionInfo } from "../../common" + +export default function () { + const params = useParams() + const user = createAsync(() => querySessionInfo(params.id!)) + + return ( + <div data-page="workspace-[id]"> + <div data-slot="sections"> + <Show when={user()?.isAdmin}> + <GraphSection /> + </Show> + <UsageSection /> + </div> + </div> + ) +} diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.module.css b/packages/console/app/src/routes/workspace/[id]/usage/usage-section.module.css similarity index 100% rename from packages/console/app/src/routes/workspace/[id]/usage-section.module.css rename to packages/console/app/src/routes/workspace/[id]/usage/usage-section.module.css diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx b/packages/console/app/src/routes/workspace/[id]/usage/usage-section.tsx similarity index 99% rename from packages/console/app/src/routes/workspace/[id]/usage-section.tsx rename to packages/console/app/src/routes/workspace/[id]/usage/usage-section.tsx index a20a5bf0d1..2cf8ef850a 100644 --- a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/usage/usage-section.tsx @@ -1,7 +1,7 @@ import { Billing } from "@opencode-ai/console-core/billing.js" import { createAsync, query, useParams } from "@solidjs/router" import { createMemo, For, Show, Switch, Match, createEffect, createSignal } from "solid-js" -import { formatDateUTC, formatDateForTable } from "../common" +import { formatDateUTC, formatDateForTable } from "../../common" import { withActor } from "~/context/auth.withActor" import { IconChevronLeft, IconChevronRight, IconBreakdown } from "~/component/icon" import styles from "./usage-section.module.css" From 75cae81f75ad3058fbace67e0674aef30b9021c7 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 03:12:11 -0400 Subject: [PATCH 097/200] zen: add Go page --- packages/console/app/src/i18n/ar.ts | 7 +++--- packages/console/app/src/i18n/br.ts | 7 +++--- packages/console/app/src/i18n/da.ts | 7 +++--- packages/console/app/src/i18n/de.ts | 7 +++--- packages/console/app/src/i18n/en.ts | 7 +++--- packages/console/app/src/i18n/es.ts | 7 +++--- packages/console/app/src/i18n/fr.ts | 7 +++--- packages/console/app/src/i18n/it.ts | 7 +++--- packages/console/app/src/i18n/ja.ts | 7 +++--- packages/console/app/src/i18n/ko.ts | 7 +++--- packages/console/app/src/i18n/no.ts | 7 +++--- packages/console/app/src/i18n/pl.ts | 7 +++--- packages/console/app/src/i18n/ru.ts | 7 +++--- packages/console/app/src/i18n/th.ts | 7 +++--- packages/console/app/src/i18n/tr.ts | 7 +++--- packages/console/app/src/i18n/zh.ts | 7 +++--- packages/console/app/src/i18n/zht.ts | 7 +++--- packages/console/app/src/routes/go/index.tsx | 2 +- .../console/app/src/routes/workspace/[id].tsx | 6 +++++ .../routes/workspace/[id]/billing/index.tsx | 4 ---- .../src/routes/workspace/[id]/go/index.tsx | 11 +++++++++ .../{billing => go}/lite-section.module.css | 0 .../[id]/{billing => go}/lite-section.tsx | 24 +++++++++++++------ 23 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 packages/console/app/src/routes/workspace/[id]/go/index.tsx rename packages/console/app/src/routes/workspace/[id]/{billing => go}/lite-section.module.css (100%) rename packages/console/app/src/routes/workspace/[id]/{billing => go}/lite-section.tsx (93%) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index a175309208..5a03eea09c 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -411,6 +411,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "سيتم خصم المبلغ من بطاقتك عند تفعيل اشتراكك", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "الاستخدام", "workspace.nav.apiKeys": "مفاتيح API", "workspace.nav.members": "الأعضاء", @@ -617,7 +618,7 @@ export const dict = { "workspace.lite.time.minute": "دقيقة", "workspace.lite.time.minutes": "دقائق", "workspace.lite.time.fewSeconds": "بضع ثوان", - "workspace.lite.subscription.title": "اشتراك Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "أنت مشترك في OpenCode Go.", "workspace.lite.subscription.manage": "إدارة الاشتراك", "workspace.lite.subscription.rollingUsage": "الاستخدام المتجدد", @@ -627,10 +628,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "استخدم رصيدك المتوفر بعد الوصول إلى حدود الاستخدام", "workspace.lite.subscription.selectProvider": 'اختر "OpenCode Go" كمزود في إعدادات opencode الخاصة بك لاستخدام نماذج Go.', - "workspace.lite.other.title": "اشتراك Go", + "workspace.lite.black.message": + 'أنت مشترك حاليًا في OpenCode Black أو في قائمة الانتظار. يرجى إلغاء الاشتراك أولاً إذا كنت ترغب في التبديل إلى Go.', "workspace.lite.other.message": "عضو آخر في مساحة العمل هذه مشترك بالفعل في OpenCode Go. يمكن لعضو واحد فقط لكل مساحة عمل الاشتراك.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go هو اشتراك بسعر $10 شهريًا يوفر وصولاً موثوقًا إلى نماذج البرمجة المفتوحة الشائعة مع حدود استخدام سخية.", "workspace.lite.promo.modelsTitle": "ما يتضمنه", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index f395db9f49..da79d2e662 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -418,6 +418,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Seu cartão será cobrado quando sua assinatura for ativada", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Uso", "workspace.nav.apiKeys": "Chaves de API", "workspace.nav.members": "Membros", @@ -626,7 +627,7 @@ export const dict = { "workspace.lite.time.minute": "minuto", "workspace.lite.time.minutes": "minutos", "workspace.lite.time.fewSeconds": "alguns segundos", - "workspace.lite.subscription.title": "Assinatura Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Você assina o OpenCode Go.", "workspace.lite.subscription.manage": "Gerenciar Assinatura", "workspace.lite.subscription.rollingUsage": "Uso Contínuo", @@ -636,10 +637,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Use seu saldo disponível após atingir os limites de uso", "workspace.lite.subscription.selectProvider": 'Selecione "OpenCode Go" como provedor na sua configuração do opencode para usar os modelos Go.', - "workspace.lite.other.title": "Assinatura Go", + "workspace.lite.black.message": + 'Você está atualmente inscrito no OpenCode Black ou na lista de espera. Por favor, cancele a assinatura primeiro se desejar mudar para o Go.', "workspace.lite.other.message": "Outro membro neste workspace já assina o OpenCode Go. Apenas um membro por workspace pode assinar.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "O OpenCode Go é uma assinatura de $10 por mês que fornece acesso confiável a modelos abertos de codificação populares com limites de uso generosos.", "workspace.lite.promo.modelsTitle": "O que está incluído", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 6e2af12af4..5fa9e2b8ca 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -414,6 +414,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Dit kort vil blive debiteret, når dit abonnement er aktiveret", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Brug", "workspace.nav.apiKeys": "API-nøgler", "workspace.nav.members": "Medlemmer", @@ -622,7 +623,7 @@ export const dict = { "workspace.lite.time.minute": "minut", "workspace.lite.time.minutes": "minutter", "workspace.lite.time.fewSeconds": "et par sekunder", - "workspace.lite.subscription.title": "Go-abonnement", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Du abonnerer på OpenCode Go.", "workspace.lite.subscription.manage": "Administrer abonnement", "workspace.lite.subscription.rollingUsage": "Løbende forbrug", @@ -632,10 +633,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Brug din tilgængelige saldo, når du har nået forbrugsgrænserne", "workspace.lite.subscription.selectProvider": 'Vælg "OpenCode Go" som udbyder i din opencode-konfiguration for at bruge Go-modeller.', - "workspace.lite.other.title": "Go-abonnement", + "workspace.lite.black.message": + 'Du abonnerer i øjeblikket på OpenCode Black eller er på venteliste. Afmeld venligst først, hvis du vil skifte til Go.', "workspace.lite.other.message": "Et andet medlem i dette workspace abonnerer allerede på OpenCode Go. Kun ét medlem pr. workspace kan abonnere.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go er et abonnement til $10 om måneden, der giver pålidelig adgang til populære åbne kodningsmodeller med generøse forbrugsgrænser.", "workspace.lite.promo.modelsTitle": "Hvad er inkluderet", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 87094a28f1..29bebc908e 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -417,6 +417,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Deine Karte wird belastet, sobald dein Abonnement aktiviert ist", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Nutzung", "workspace.nav.apiKeys": "API Keys", "workspace.nav.members": "Mitglieder", @@ -625,7 +626,7 @@ export const dict = { "workspace.lite.time.minute": "Minute", "workspace.lite.time.minutes": "Minuten", "workspace.lite.time.fewSeconds": "einige Sekunden", - "workspace.lite.subscription.title": "Go-Abonnement", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Du hast OpenCode Go abonniert.", "workspace.lite.subscription.manage": "Abo verwalten", "workspace.lite.subscription.rollingUsage": "Fortlaufende Nutzung", @@ -635,10 +636,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Nutze dein verfügbares Guthaben, nachdem die Nutzungslimits erreicht sind", "workspace.lite.subscription.selectProvider": 'Wähle "OpenCode Go" als Anbieter in deiner opencode-Konfiguration, um Go-Modelle zu verwenden.', - "workspace.lite.other.title": "Go-Abonnement", + "workspace.lite.black.message": + 'Du hast derzeit OpenCode Black abonniert oder stehst auf der Warteliste. Bitte kündige zuerst, wenn du zu Go wechseln möchtest.', "workspace.lite.other.message": "Ein anderes Mitglied in diesem Workspace hat OpenCode Go bereits abonniert. Nur ein Mitglied pro Workspace kann abonnieren.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go ist ein Abonnement für $10 pro Monat, das zuverlässigen Zugriff auf beliebte offene Coding-Modelle mit großzügigen Nutzungslimits bietet.", "workspace.lite.promo.modelsTitle": "Was enthalten ist", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 1f1773dd41..dca14bb875 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -411,6 +411,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Your card will be charged when your subscription is activated", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Usage", "workspace.nav.apiKeys": "API Keys", "workspace.nav.members": "Members", @@ -619,7 +620,7 @@ export const dict = { "workspace.lite.time.minute": "minute", "workspace.lite.time.minutes": "minutes", "workspace.lite.time.fewSeconds": "a few seconds", - "workspace.lite.subscription.title": "Go Subscription", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "You are subscribed to OpenCode Go.", "workspace.lite.subscription.manage": "Manage Subscription", "workspace.lite.subscription.rollingUsage": "Rolling Usage", @@ -629,10 +630,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Use your available balance after reaching the usage limits", "workspace.lite.subscription.selectProvider": 'Select "OpenCode Go" as the provider in your opencode configuration to use Go models.', - "workspace.lite.other.title": "Go Subscription", + "workspace.lite.black.message": + "You're currently subscribed to OpenCode Black or on the waitlist. Please unsubscribe first if you'd like to switch to Go.", "workspace.lite.other.message": "Another member in this workspace is already subscribed to OpenCode Go. Only one member per workspace can subscribe.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go is a $10 per month subscription that provides reliable access to popular open coding models with generous usage limits.", "workspace.lite.promo.modelsTitle": "What's Included", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 19a9842d56..f1a95b2be9 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -419,6 +419,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Tu tarjeta se cargará cuando tu suscripción se active", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Uso", "workspace.nav.apiKeys": "Claves API", "workspace.nav.members": "Miembros", @@ -627,7 +628,7 @@ export const dict = { "workspace.lite.time.minute": "minuto", "workspace.lite.time.minutes": "minutos", "workspace.lite.time.fewSeconds": "unos pocos segundos", - "workspace.lite.subscription.title": "Suscripción Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Estás suscrito a OpenCode Go.", "workspace.lite.subscription.manage": "Gestionar Suscripción", "workspace.lite.subscription.rollingUsage": "Uso Continuo", @@ -637,10 +638,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Usa tu saldo disponible después de alcanzar los límites de uso", "workspace.lite.subscription.selectProvider": 'Selecciona "OpenCode Go" como proveedor en tu configuración de opencode para usar los modelos Go.', - "workspace.lite.other.title": "Suscripción Go", + "workspace.lite.black.message": + 'Actualmente estás suscrito a OpenCode Black o estás en la lista de espera. Por favor, cancela la suscripción primero si deseas cambiar a Go.', "workspace.lite.other.message": "Otro miembro de este espacio de trabajo ya está suscrito a OpenCode Go. Solo un miembro por espacio de trabajo puede suscribirse.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go es una suscripción de $10 al mes que proporciona acceso confiable a modelos de codificación abiertos populares con generosos límites de uso.", "workspace.lite.promo.modelsTitle": "Qué incluye", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index cf6d5e95b8..7e2ff66dbe 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -419,6 +419,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Votre carte sera débitée lorsque votre abonnement sera activé", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Utilisation", "workspace.nav.apiKeys": "Clés API", "workspace.nav.members": "Membres", @@ -631,7 +632,7 @@ export const dict = { "workspace.lite.time.minute": "minute", "workspace.lite.time.minutes": "minutes", "workspace.lite.time.fewSeconds": "quelques secondes", - "workspace.lite.subscription.title": "Abonnement Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Vous êtes abonné à OpenCode Go.", "workspace.lite.subscription.manage": "Gérer l'abonnement", "workspace.lite.subscription.rollingUsage": "Utilisation glissante", @@ -642,10 +643,10 @@ export const dict = { "Utilisez votre solde disponible après avoir atteint les limites d'utilisation", "workspace.lite.subscription.selectProvider": 'Sélectionnez "OpenCode Go" comme fournisseur dans votre configuration opencode pour utiliser les modèles Go.', - "workspace.lite.other.title": "Abonnement Go", + "workspace.lite.black.message": + "Vous êtes actuellement abonné à OpenCode Black ou sur liste d'attente. Veuillez d'abord vous désabonner si vous souhaitez passer à Go.", "workspace.lite.other.message": "Un autre membre de cet espace de travail est déjà abonné à OpenCode Go. Un seul membre par espace de travail peut s'abonner.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go est un abonnement à 10 $ par mois qui offre un accès fiable aux modèles de codage ouverts populaires avec des limites d'utilisation généreuses.", "workspace.lite.promo.modelsTitle": "Ce qui est inclus", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index b967f078c7..c579a48637 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -417,6 +417,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "La tua carta verrà addebitata quando il tuo abbonamento sarà attivato", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Utilizzo", "workspace.nav.apiKeys": "Chiavi API", "workspace.nav.members": "Membri", @@ -625,7 +626,7 @@ export const dict = { "workspace.lite.time.minute": "minuto", "workspace.lite.time.minutes": "minuti", "workspace.lite.time.fewSeconds": "pochi secondi", - "workspace.lite.subscription.title": "Abbonamento Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Sei abbonato a OpenCode Go.", "workspace.lite.subscription.manage": "Gestisci Abbonamento", "workspace.lite.subscription.rollingUsage": "Utilizzo Continuativo", @@ -635,10 +636,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Usa il tuo saldo disponibile dopo aver raggiunto i limiti di utilizzo", "workspace.lite.subscription.selectProvider": 'Seleziona "OpenCode Go" come provider nella tua configurazione opencode per utilizzare i modelli Go.', - "workspace.lite.other.title": "Abbonamento Go", + "workspace.lite.black.message": + "Attualmente sei abbonato a OpenCode Black o sei in lista d'attesa. Annulla l'iscrizione prima se desideri passare a Go.", "workspace.lite.other.message": "Un altro membro in questo workspace è già abbonato a OpenCode Go. Solo un membro per workspace può abbonarsi.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go è un abbonamento a $10 al mese che fornisce un accesso affidabile a popolari modelli di coding aperti con generosi limiti di utilizzo.", "workspace.lite.promo.modelsTitle": "Cosa è incluso", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 759d5e7c72..020f680050 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -416,6 +416,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "サブスクリプションが有効化された時点でカードに請求されます", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "利用", "workspace.nav.apiKeys": "APIキー", "workspace.nav.members": "メンバー", @@ -625,7 +626,7 @@ export const dict = { "workspace.lite.time.minute": "分", "workspace.lite.time.minutes": "分", "workspace.lite.time.fewSeconds": "数秒", - "workspace.lite.subscription.title": "Goサブスクリプション", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "あなたは OpenCode Go を購読しています。", "workspace.lite.subscription.manage": "サブスクリプションの管理", "workspace.lite.subscription.rollingUsage": "ローリング利用量", @@ -635,10 +636,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "利用限度額に達したら利用可能な残高を使用する", "workspace.lite.subscription.selectProvider": "Go モデルを使用するには、opencode の設定で「OpenCode Go」をプロバイダーとして選択してください。", - "workspace.lite.other.title": "Goサブスクリプション", + "workspace.lite.black.message": + '現在 OpenCode Black を購読中、またはウェイティングリストに登録されています。Go に切り替える場合は、先に登録を解除してください。', "workspace.lite.other.message": "このワークスペースの別のメンバーが既に OpenCode Go を購読しています。ワークスペースにつき1人のメンバーのみが購読できます。", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Goは月額$10のサブスクリプションプランで、人気のオープンコーディングモデルへの安定したアクセスを十分な利用枠で提供します。", "workspace.lite.promo.modelsTitle": "含まれるもの", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index 9a5237f332..b5c6efc444 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -410,6 +410,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "구독이 활성화되면 카드에 청구됩니다", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "사용량", "workspace.nav.apiKeys": "API 키", "workspace.nav.members": "멤버", @@ -617,7 +618,7 @@ export const dict = { "workspace.lite.time.minute": "분", "workspace.lite.time.minutes": "분", "workspace.lite.time.fewSeconds": "몇 초", - "workspace.lite.subscription.title": "Go 구독", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "현재 OpenCode Go를 구독 중입니다.", "workspace.lite.subscription.manage": "구독 관리", "workspace.lite.subscription.rollingUsage": "롤링 사용량", @@ -627,10 +628,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "사용 한도 도달 후에는 보유 잔액 사용", "workspace.lite.subscription.selectProvider": 'Go 모델을 사용하려면 opencode 설정에서 "OpenCode Go"를 공급자로 선택하세요.', - "workspace.lite.other.title": "Go 구독", + "workspace.lite.black.message": + '현재 OpenCode Black을 구독 중이거나 대기 명단에 등록되어 있습니다. Go로 전환하려면 먼저 구독을 취소해 주세요.', "workspace.lite.other.message": "이 워크스페이스의 다른 멤버가 이미 OpenCode Go를 구독 중입니다. 워크스페이스당 한 명의 멤버만 구독할 수 있습니다.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go는 넉넉한 사용 한도와 함께 인기 있는 오픈 코딩 모델에 대한 안정적인 액세스를 제공하는 월 $10의 구독입니다.", "workspace.lite.promo.modelsTitle": "포함 내역", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index fa44e535eb..31dc8ee105 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -415,6 +415,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Kortet ditt vil bli belastet når abonnementet aktiveres", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Bruk", "workspace.nav.apiKeys": "API-nøkler", "workspace.nav.members": "Medlemmer", @@ -623,7 +624,7 @@ export const dict = { "workspace.lite.time.minute": "minutt", "workspace.lite.time.minutes": "minutter", "workspace.lite.time.fewSeconds": "noen få sekunder", - "workspace.lite.subscription.title": "Go-abonnement", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Du abonnerer på OpenCode Go.", "workspace.lite.subscription.manage": "Administrer abonnement", "workspace.lite.subscription.rollingUsage": "Løpende bruk", @@ -633,10 +634,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Bruk din tilgjengelige saldo etter å ha nådd bruksgrensene", "workspace.lite.subscription.selectProvider": 'Velg "OpenCode Go" som leverandør i opencode-konfigurasjonen din for å bruke Go-modeller.', - "workspace.lite.other.title": "Go-abonnement", + "workspace.lite.black.message": + 'Du abonnerer for øyeblikket på OpenCode Black eller står på venteliste. Vennligst avslutt abonnementet først hvis du vil bytte til Go.', "workspace.lite.other.message": "Et annet medlem i dette arbeidsområdet abonnerer allerede på OpenCode Go. Kun ett medlem per arbeidsområde kan abonnere.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go er et abonnement til $10 per måned som gir pålitelig tilgang til populære åpne kodemodeller med rause bruksgrenser.", "workspace.lite.promo.modelsTitle": "Hva som er inkludert", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 7c7300ab95..dde32158a5 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -416,6 +416,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Twoja karta zostanie obciążona po aktywacji subskrypcji", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Użycie", "workspace.nav.apiKeys": "Klucze API", "workspace.nav.members": "Członkowie", @@ -624,7 +625,7 @@ export const dict = { "workspace.lite.time.minute": "minuta", "workspace.lite.time.minutes": "minut(y)", "workspace.lite.time.fewSeconds": "kilka sekund", - "workspace.lite.subscription.title": "Subskrypcja Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Subskrybujesz OpenCode Go.", "workspace.lite.subscription.manage": "Zarządzaj subskrypcją", "workspace.lite.subscription.rollingUsage": "Użycie kroczące", @@ -634,10 +635,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Użyj dostępnego salda po osiągnięciu limitów użycia", "workspace.lite.subscription.selectProvider": 'Wybierz "OpenCode Go" jako dostawcę w konfiguracji opencode, aby używać modeli Go.', - "workspace.lite.other.title": "Subskrypcja Go", + "workspace.lite.black.message": + 'Obecnie subskrybujesz OpenCode Black lub jesteś na liście oczekujących. Jeśli chcesz przejść na Go, najpierw anuluj subskrypcję.', "workspace.lite.other.message": "Inny członek tego obszaru roboczego już subskrybuje OpenCode Go. Tylko jeden członek na obszar roboczy może subskrybować.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go to subskrypcja za $10 miesięcznie, która zapewnia niezawodny dostęp do popularnych otwartych modeli do kodowania z hojnymi limitami użycia.", "workspace.lite.promo.modelsTitle": "Co zawiera", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 77e36bc992..4a84e91ccd 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -421,6 +421,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "С вашей карты будет списана оплата при активации подписки", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Использование", "workspace.nav.apiKeys": "API Ключи", "workspace.nav.members": "Участники", @@ -630,7 +631,7 @@ export const dict = { "workspace.lite.time.minute": "минута", "workspace.lite.time.minutes": "минут", "workspace.lite.time.fewSeconds": "несколько секунд", - "workspace.lite.subscription.title": "Подписка Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Вы подписаны на OpenCode Go.", "workspace.lite.subscription.manage": "Управление подпиской", "workspace.lite.subscription.rollingUsage": "Скользящее использование", @@ -640,10 +641,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Использовать доступный баланс после достижения лимитов", "workspace.lite.subscription.selectProvider": 'Выберите "OpenCode Go" в качестве провайдера в настройках opencode для использования моделей Go.', - "workspace.lite.other.title": "Подписка Go", + "workspace.lite.black.message": + 'Вы подписаны на OpenCode Black или находитесь в списке ожидания. Пожалуйста, сначала отмените подписку, если хотите перейти на Go.', "workspace.lite.other.message": "Другой участник в этом рабочем пространстве уже подписан на OpenCode Go. Только один участник в рабочем пространстве может оформить подписку.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go — это подписка за $10 в месяц, которая предоставляет надежный доступ к популярным открытым моделям для кодинга с щедрыми лимитами использования.", "workspace.lite.promo.modelsTitle": "Что включено", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 9d1e92fa8d..aabfea257d 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -413,6 +413,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "บัตรของคุณจะถูกเรียกเก็บเงินเมื่อการสมัครสมาชิกของคุณถูกเปิดใช้งาน", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "การใช้งาน", "workspace.nav.apiKeys": "API Keys", "workspace.nav.members": "สมาชิก", @@ -621,7 +622,7 @@ export const dict = { "workspace.lite.time.minute": "นาที", "workspace.lite.time.minutes": "นาที", "workspace.lite.time.fewSeconds": "ไม่กี่วินาที", - "workspace.lite.subscription.title": "การสมัครสมาชิก Go", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "คุณได้สมัครสมาชิก OpenCode Go แล้ว", "workspace.lite.subscription.manage": "จัดการการสมัครสมาชิก", "workspace.lite.subscription.rollingUsage": "การใช้งานแบบหมุนเวียน", @@ -631,10 +632,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "ใช้ยอดคงเหลือของคุณหลังจากถึงขีดจำกัดการใช้งาน", "workspace.lite.subscription.selectProvider": 'เลือก "OpenCode Go" เป็นผู้ให้บริการในการตั้งค่า opencode ของคุณเพื่อใช้โมเดล Go', - "workspace.lite.other.title": "การสมัครสมาชิก Go", + "workspace.lite.black.message": + 'ขณะนี้คุณสมัครสมาชิก OpenCode Black หรืออยู่ในรายการรอ โปรดยกเลิกการสมัครก่อนหากต้องการเปลี่ยนไปใช้ Go', "workspace.lite.other.message": "สมาชิกคนอื่นใน Workspace นี้ได้สมัคร OpenCode Go แล้ว สามารถสมัครได้เพียงหนึ่งคนต่อหนึ่ง Workspace เท่านั้น", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go เป็นการสมัครสมาชิกราคา 10 ดอลลาร์ต่อเดือน ที่ให้การเข้าถึงโมเดลโอเพนโค้ดดิงยอดนิยมได้อย่างเสถียร ด้วยขีดจำกัดการใช้งานที่ครอบคลุม", "workspace.lite.promo.modelsTitle": "สิ่งที่รวมอยู่ด้วย", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 84ad9a7e38..6d6e414d11 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -418,6 +418,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "Aboneliğiniz aktive edildiğinde kartınızdan ödeme alınacaktır", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "Kullanım", "workspace.nav.apiKeys": "API Anahtarları", "workspace.nav.members": "Üyeler", @@ -626,7 +627,7 @@ export const dict = { "workspace.lite.time.minute": "dakika", "workspace.lite.time.minutes": "dakika", "workspace.lite.time.fewSeconds": "birkaç saniye", - "workspace.lite.subscription.title": "Go Aboneliği", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "OpenCode Go abonesisiniz.", "workspace.lite.subscription.manage": "Aboneliği Yönet", "workspace.lite.subscription.rollingUsage": "Devam Eden Kullanım", @@ -636,10 +637,10 @@ export const dict = { "workspace.lite.subscription.useBalance": "Kullanım limitlerine ulaştıktan sonra mevcut bakiyenizi kullanın", "workspace.lite.subscription.selectProvider": 'Go modellerini kullanmak için opencode yapılandırmanızda "OpenCode Go"\'yu sağlayıcı olarak seçin.', - "workspace.lite.other.title": "Go Aboneliği", + "workspace.lite.black.message": + "Şu anda OpenCode Black abonesisiniz veya bekleme listesindesiniz. Go'ya geçmek istiyorsanız lütfen önce aboneliğinizi iptal edin.", "workspace.lite.other.message": "Bu çalışma alanındaki başka bir üye zaten OpenCode Go abonesi. Çalışma alanı başına yalnızca bir üye abone olabilir.", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go, cömert kullanım limitleriyle popüler açık kodlama modellerine güvenilir erişim sağlayan aylık 10$'lık bir aboneliktir.", "workspace.lite.promo.modelsTitle": "Neler Dahil", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index a3527ae3e0..ccb3a554d0 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -396,6 +396,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "您的卡将在订阅激活时扣费", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "使用量", "workspace.nav.apiKeys": "API 密钥", "workspace.nav.members": "成员", @@ -601,7 +602,7 @@ export const dict = { "workspace.lite.time.minute": "分钟", "workspace.lite.time.minutes": "分钟", "workspace.lite.time.fewSeconds": "几秒钟", - "workspace.lite.subscription.title": "Go 订阅", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "您已订阅 OpenCode Go。", "workspace.lite.subscription.manage": "管理订阅", "workspace.lite.subscription.rollingUsage": "滚动用量", @@ -611,9 +612,9 @@ export const dict = { "workspace.lite.subscription.useBalance": "达到使用限额后使用您的可用余额", "workspace.lite.subscription.selectProvider": "在你的 opencode 配置中选择「OpenCode Go」作为提供商,即可使用 Go 模型。", - "workspace.lite.other.title": "Go 订阅", + "workspace.lite.black.message": + '您当前已订阅 OpenCode Black 或在候补名单中。如需切换到 Go,请先取消订阅。', "workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Go。每个工作区只有一名成员可以订阅。", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go 是一个每月 $10 的订阅计划,提供对主流开源编码模型的稳定访问,并配备充足的使用额度。", "workspace.lite.promo.modelsTitle": "包含模型", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 1167d5a6c7..bd12783e20 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -397,6 +397,7 @@ export const dict = { "black.subscribe.success.chargeNotice": "你的卡片將在訂閱啟用時扣款", "workspace.nav.zen": "Zen", + "workspace.nav.go": "Go", "workspace.nav.usage": "使用量", "workspace.nav.apiKeys": "API 金鑰", "workspace.nav.members": "成員", @@ -602,7 +603,7 @@ export const dict = { "workspace.lite.time.minute": "分鐘", "workspace.lite.time.minutes": "分鐘", "workspace.lite.time.fewSeconds": "幾秒", - "workspace.lite.subscription.title": "Go 訂閱", + "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "您已訂閱 OpenCode Go。", "workspace.lite.subscription.manage": "管理訂閱", "workspace.lite.subscription.rollingUsage": "滾動使用量", @@ -612,9 +613,9 @@ export const dict = { "workspace.lite.subscription.useBalance": "達到使用限制後使用您的可用餘額", "workspace.lite.subscription.selectProvider": "在您的 opencode 設定中選擇「OpenCode Go」作為提供商,即可使用 Go 模型。", - "workspace.lite.other.title": "Go 訂閱", + "workspace.lite.black.message": + '您目前已訂閱 OpenCode Black 或在候補名單中。若要切換至 Go,請先取消訂閱。', "workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Go。每個工作區只能有一位成員訂閱。", - "workspace.lite.promo.title": "OpenCode Go", "workspace.lite.promo.description": "OpenCode Go 是一個每月 $10 的訂閱方案,提供對主流開放原始碼編碼模型的穩定存取,並配備充足的使用額度。", "workspace.lite.promo.modelsTitle": "包含模型", diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index dcd909ab40..8f1930bd7b 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -205,7 +205,7 @@ function LimitsGraph(props: { href: string }) { export default function Home() { const workspaceID = createAsync(() => checkLoggedIn()) - const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/billing` : "/auth")) + const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/go` : "/auth")) const i18n = useI18n() const language = useLanguage() return ( diff --git a/packages/console/app/src/routes/workspace/[id].tsx b/packages/console/app/src/routes/workspace/[id].tsx index a36368fd0f..7a8e1616f1 100644 --- a/packages/console/app/src/routes/workspace/[id].tsx +++ b/packages/console/app/src/routes/workspace/[id].tsx @@ -19,6 +19,9 @@ export default function WorkspaceLayout(props: RouteSectionProps) { <A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button> {i18n.t("workspace.nav.zen")} </A> + <A href={`/workspace/${params.id}/go`} activeClass="active" data-nav-button> + {i18n.t("workspace.nav.go")} + </A> <A href={`/workspace/${params.id}/usage`} activeClass="active" data-nav-button> {i18n.t("workspace.nav.usage")} </A> @@ -44,6 +47,9 @@ export default function WorkspaceLayout(props: RouteSectionProps) { <A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button> {i18n.t("workspace.nav.zen")} </A> + <A href={`/workspace/${params.id}/go`} activeClass="active" data-nav-button> + {i18n.t("workspace.nav.go")} + </A> <A href={`/workspace/${params.id}/usage`} activeClass="active" data-nav-button> {i18n.t("workspace.nav.usage")} </A> diff --git a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx index e039a09ef8..4a7dc24888 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx @@ -3,7 +3,6 @@ import { BillingSection } from "./billing-section" import { ReloadSection } from "./reload-section" import { PaymentSection } from "./payment-section" import { BlackSection } from "./black-section" -import { LiteSection } from "./lite-section" import { createMemo, Show } from "solid-js" import { createAsync, useParams } from "@solidjs/router" import { queryBillingInfo, querySessionInfo } from "../../common" @@ -21,9 +20,6 @@ export default function () { <Show when={isBlack()}> <BlackSection /> </Show> - <Show when={!isBlack()}> - <LiteSection /> - </Show> <BillingSection /> <Show when={billingInfo()?.customerID}> <ReloadSection /> diff --git a/packages/console/app/src/routes/workspace/[id]/go/index.tsx b/packages/console/app/src/routes/workspace/[id]/go/index.tsx new file mode 100644 index 0000000000..116938a121 --- /dev/null +++ b/packages/console/app/src/routes/workspace/[id]/go/index.tsx @@ -0,0 +1,11 @@ +import { LiteSection } from "./lite-section" + +export default function () { + return ( + <div data-page="workspace-[id]"> + <div data-slot="sections"> + <LiteSection /> + </div> + </div> + ) +} diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css similarity index 100% rename from packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css rename to packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx similarity index 93% rename from packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx rename to packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index f67775d79c..282411e8c3 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -1,6 +1,6 @@ import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router" import { createStore } from "solid-js/store" -import { Show } from "solid-js" +import { createMemo, Show } from "solid-js" import { Billing } from "@opencode-ai/console-core/billing.js" import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js" import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js" @@ -138,6 +138,8 @@ export function LiteSection() { const params = useParams() const i18n = useI18n() const language = useLanguage() + const billingInfo = createAsync(() => queryBillingInfo(params.id!)) + const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked) const lite = createAsync(() => queryLiteSubscription(params.id!)) const sessionAction = useAction(createSessionUrl) const sessionSubmission = useSubmission(createSessionUrl) @@ -166,11 +168,19 @@ export function LiteSection() { return ( <> - <Show when={lite() && lite()!.mine && lite()!}> + <Show when={isBlack()}> + <section class={styles.root}> + <div data-slot="section-title"> + <h2>{i18n.t("workspace.lite.title")}</h2> + </div> + <p data-slot="other-message">{i18n.t("workspace.lite.black.message")}</p> + </section> + </Show> + <Show when={!isBlack() && lite() && lite()!.mine && lite()!}> {(sub) => ( <section class={styles.root}> <div data-slot="section-title"> - <h2>{i18n.t("workspace.lite.subscription.title")}</h2> + <h2>{i18n.t("workspace.lite.title")}</h2> <div data-slot="title-row"> <p>{i18n.t("workspace.lite.subscription.message")}</p> <button @@ -248,18 +258,18 @@ export function LiteSection() { </section> )} </Show> - <Show when={lite() && !lite()!.mine}> + <Show when={!isBlack() && lite() && !lite()!.mine}> <section class={styles.root}> <div data-slot="section-title"> - <h2>{i18n.t("workspace.lite.other.title")}</h2> + <h2>{i18n.t("workspace.lite.title")}</h2> </div> <p data-slot="other-message">{i18n.t("workspace.lite.other.message")}</p> </section> </Show> - <Show when={lite() === null}> + <Show when={!isBlack() && lite() === null}> <section class={styles.root}> <div data-slot="section-title"> - <h2>{i18n.t("workspace.lite.promo.title")}</h2> + <h2>{i18n.t("workspace.lite.title")}</h2> </div> <p data-slot="promo-description">{i18n.t("workspace.lite.promo.description")}</p> <h3 data-slot="promo-models-title">{i18n.t("workspace.lite.promo.modelsTitle")}</h3> From 4a81df190c58c29418d8c32e9402cf71afa61bc8 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 03:31:24 -0400 Subject: [PATCH 098/200] zen: add alipay for go sub --- packages/console/app/src/component/icon.tsx | 9 +++++++++ packages/console/app/src/i18n/ar.ts | 3 ++- packages/console/app/src/i18n/br.ts | 3 ++- packages/console/app/src/i18n/da.ts | 3 ++- packages/console/app/src/i18n/de.ts | 3 ++- packages/console/app/src/i18n/en.ts | 1 + packages/console/app/src/i18n/es.ts | 3 ++- packages/console/app/src/i18n/fr.ts | 1 + packages/console/app/src/i18n/it.ts | 1 + packages/console/app/src/i18n/ja.ts | 3 ++- packages/console/app/src/i18n/ko.ts | 3 ++- packages/console/app/src/i18n/no.ts | 3 ++- packages/console/app/src/i18n/pl.ts | 3 ++- packages/console/app/src/i18n/ru.ts | 3 ++- packages/console/app/src/i18n/th.ts | 3 ++- packages/console/app/src/i18n/tr.ts | 1 + packages/console/app/src/i18n/zh.ts | 4 ++-- packages/console/app/src/i18n/zht.ts | 4 ++-- .../routes/workspace/[id]/billing/billing-section.tsx | 8 +++++++- packages/console/core/src/billing.ts | 2 +- 20 files changed, 47 insertions(+), 17 deletions(-) diff --git a/packages/console/app/src/component/icon.tsx b/packages/console/app/src/component/icon.tsx index 1225aeb10c..8d3c71656e 100644 --- a/packages/console/app/src/component/icon.tsx +++ b/packages/console/app/src/component/icon.tsx @@ -111,6 +111,15 @@ export function IconStripe(props: JSX.SvgSVGAttributes<SVGSVGElement>) { ) } +export function IconAlipay(props: JSX.SvgSVGAttributes<SVGSVGElement>) { + return ( + <svg {...props} viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> + <path d="M2.541 0H13.5a2.55 2.55 0 0 1 2.54 2.563v8.297c-.006 0-.531-.046-2.978-.813-.412-.14-.916-.327-1.479-.536q-.456-.17-.957-.353a13 13 0 0 0 1.325-3.373H8.822V4.649h3.831v-.634h-3.83V2.121H7.26c-.274 0-.274.273-.274.273v1.621H3.11v.634h3.875v1.136h-3.2v.634H9.99c-.227.789-.532 1.53-.894 2.202-2.013-.67-4.161-1.212-5.51-.878-.864.214-1.42.597-1.746.998-1.499 1.84-.424 4.633 2.741 4.633 1.872 0 3.675-1.053 5.072-2.787 2.08 1.008 6.37 2.738 6.387 2.745v.105A2.55 2.55 0 0 1 13.5 16H2.541A2.55 2.55 0 0 1 0 13.437V2.563A2.55 2.55 0 0 1 2.541 0" /> + <path d="M2.309 9.27c-1.22 1.073-.49 3.034 1.978 3.034 1.434 0 2.868-.925 3.994-2.406-1.602-.789-2.959-1.353-4.425-1.207-.397.04-1.14.217-1.547.58Z" /> + </svg> + ) +} + export function IconChevron(props: JSX.SvgSVGAttributes<SVGSVGElement>) { return ( <svg {...props} width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg"> diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 5a03eea09c..081535a3a4 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -537,6 +537,7 @@ export const dict = { "workspace.billing.loading": "جارٍ التحميل...", "workspace.billing.addAction": "إضافة", "workspace.billing.addBalance": "إضافة رصيد", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "مرتبط بـ Stripe", "workspace.billing.manage": "إدارة", "workspace.billing.enable": "تمكين الفوترة", @@ -629,7 +630,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'اختر "OpenCode Go" كمزود في إعدادات opencode الخاصة بك لاستخدام نماذج Go.', "workspace.lite.black.message": - 'أنت مشترك حاليًا في OpenCode Black أو في قائمة الانتظار. يرجى إلغاء الاشتراك أولاً إذا كنت ترغب في التبديل إلى Go.', + "أنت مشترك حاليًا في OpenCode Black أو في قائمة الانتظار. يرجى إلغاء الاشتراك أولاً إذا كنت ترغب في التبديل إلى Go.", "workspace.lite.other.message": "عضو آخر في مساحة العمل هذه مشترك بالفعل في OpenCode Go. يمكن لعضو واحد فقط لكل مساحة عمل الاشتراك.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index da79d2e662..96f40499e7 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -545,6 +545,7 @@ export const dict = { "workspace.billing.loading": "Carregando...", "workspace.billing.addAction": "Adicionar", "workspace.billing.addBalance": "Adicionar Saldo", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Vinculado ao Stripe", "workspace.billing.manage": "Gerenciar", "workspace.billing.enable": "Ativar Faturamento", @@ -638,7 +639,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Selecione "OpenCode Go" como provedor na sua configuração do opencode para usar os modelos Go.', "workspace.lite.black.message": - 'Você está atualmente inscrito no OpenCode Black ou na lista de espera. Por favor, cancele a assinatura primeiro se desejar mudar para o Go.', + "Você está atualmente inscrito no OpenCode Black ou na lista de espera. Por favor, cancele a assinatura primeiro se desejar mudar para o Go.", "workspace.lite.other.message": "Outro membro neste workspace já assina o OpenCode Go. Apenas um membro por workspace pode assinar.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 5fa9e2b8ca..7ed1114857 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -541,6 +541,7 @@ export const dict = { "workspace.billing.loading": "Indlæser...", "workspace.billing.addAction": "Tilføj", "workspace.billing.addBalance": "Tilføj saldo", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Forbundet til Stripe", "workspace.billing.manage": "Administrer", "workspace.billing.enable": "Aktiver fakturering", @@ -634,7 +635,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Vælg "OpenCode Go" som udbyder i din opencode-konfiguration for at bruge Go-modeller.', "workspace.lite.black.message": - 'Du abonnerer i øjeblikket på OpenCode Black eller er på venteliste. Afmeld venligst først, hvis du vil skifte til Go.', + "Du abonnerer i øjeblikket på OpenCode Black eller er på venteliste. Afmeld venligst først, hvis du vil skifte til Go.", "workspace.lite.other.message": "Et andet medlem i dette workspace abonnerer allerede på OpenCode Go. Kun ét medlem pr. workspace kan abonnere.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 29bebc908e..bd81c9bf80 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -544,6 +544,7 @@ export const dict = { "workspace.billing.loading": "Lade...", "workspace.billing.addAction": "Hinzufügen", "workspace.billing.addBalance": "Guthaben aufladen", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Mit Stripe verbunden", "workspace.billing.manage": "Verwalten", "workspace.billing.enable": "Abrechnung aktivieren", @@ -637,7 +638,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Wähle "OpenCode Go" als Anbieter in deiner opencode-Konfiguration, um Go-Modelle zu verwenden.', "workspace.lite.black.message": - 'Du hast derzeit OpenCode Black abonniert oder stehst auf der Warteliste. Bitte kündige zuerst, wenn du zu Go wechseln möchtest.', + "Du hast derzeit OpenCode Black abonniert oder stehst auf der Warteliste. Bitte kündige zuerst, wenn du zu Go wechseln möchtest.", "workspace.lite.other.message": "Ein anderes Mitglied in diesem Workspace hat OpenCode Go bereits abonniert. Nur ein Mitglied pro Workspace kann abonnieren.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index dca14bb875..05c4643afe 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -538,6 +538,7 @@ export const dict = { "workspace.billing.loading": "Loading...", "workspace.billing.addAction": "Add", "workspace.billing.addBalance": "Add Balance", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Linked to Stripe", "workspace.billing.manage": "Manage", "workspace.billing.enable": "Enable Billing", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index f1a95b2be9..92486987b3 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -546,6 +546,7 @@ export const dict = { "workspace.billing.loading": "Cargando...", "workspace.billing.addAction": "Añadir", "workspace.billing.addBalance": "Añadir Saldo", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Vinculado con Stripe", "workspace.billing.manage": "Gestionar", "workspace.billing.enable": "Habilitar Facturación", @@ -639,7 +640,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Selecciona "OpenCode Go" como proveedor en tu configuración de opencode para usar los modelos Go.', "workspace.lite.black.message": - 'Actualmente estás suscrito a OpenCode Black o estás en la lista de espera. Por favor, cancela la suscripción primero si deseas cambiar a Go.', + "Actualmente estás suscrito a OpenCode Black o estás en la lista de espera. Por favor, cancela la suscripción primero si deseas cambiar a Go.", "workspace.lite.other.message": "Otro miembro de este espacio de trabajo ya está suscrito a OpenCode Go. Solo un miembro por espacio de trabajo puede suscribirse.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 7e2ff66dbe..df379fae9f 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -547,6 +547,7 @@ export const dict = { "workspace.billing.loading": "Chargement...", "workspace.billing.addAction": "Ajouter", "workspace.billing.addBalance": "Ajouter un solde", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Lié à Stripe", "workspace.billing.manage": "Gérer", "workspace.billing.enable": "Activer la facturation", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index c579a48637..24f3aa2f84 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -544,6 +544,7 @@ export const dict = { "workspace.billing.loading": "Caricamento...", "workspace.billing.addAction": "Aggiungi", "workspace.billing.addBalance": "Aggiungi Saldo", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Collegato a Stripe", "workspace.billing.manage": "Gestisci", "workspace.billing.enable": "Abilita Fatturazione", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 020f680050..f11f5052bf 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -543,6 +543,7 @@ export const dict = { "workspace.billing.loading": "読み込み中...", "workspace.billing.addAction": "追加", "workspace.billing.addBalance": "残高を追加", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Stripeと連携済み", "workspace.billing.manage": "管理", "workspace.billing.enable": "課金を有効にする", @@ -637,7 +638,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": "Go モデルを使用するには、opencode の設定で「OpenCode Go」をプロバイダーとして選択してください。", "workspace.lite.black.message": - '現在 OpenCode Black を購読中、またはウェイティングリストに登録されています。Go に切り替える場合は、先に登録を解除してください。', + "現在 OpenCode Black を購読中、またはウェイティングリストに登録されています。Go に切り替える場合は、先に登録を解除してください。", "workspace.lite.other.message": "このワークスペースの別のメンバーが既に OpenCode Go を購読しています。ワークスペースにつき1人のメンバーのみが購読できます。", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index b5c6efc444..fe33bf5457 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -537,6 +537,7 @@ export const dict = { "workspace.billing.loading": "로드 중...", "workspace.billing.addAction": "추가", "workspace.billing.addBalance": "잔액 추가", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Stripe에 연결됨", "workspace.billing.manage": "관리", "workspace.billing.enable": "결제 활성화", @@ -629,7 +630,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Go 모델을 사용하려면 opencode 설정에서 "OpenCode Go"를 공급자로 선택하세요.', "workspace.lite.black.message": - '현재 OpenCode Black을 구독 중이거나 대기 명단에 등록되어 있습니다. Go로 전환하려면 먼저 구독을 취소해 주세요.', + "현재 OpenCode Black을 구독 중이거나 대기 명단에 등록되어 있습니다. Go로 전환하려면 먼저 구독을 취소해 주세요.", "workspace.lite.other.message": "이 워크스페이스의 다른 멤버가 이미 OpenCode Go를 구독 중입니다. 워크스페이스당 한 명의 멤버만 구독할 수 있습니다.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 31dc8ee105..af2a8d59f3 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -542,6 +542,7 @@ export const dict = { "workspace.billing.loading": "Laster...", "workspace.billing.addAction": "Legg til", "workspace.billing.addBalance": "Legg til saldo", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Koblet til Stripe", "workspace.billing.manage": "Administrer", "workspace.billing.enable": "Aktiver fakturering", @@ -635,7 +636,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Velg "OpenCode Go" som leverandør i opencode-konfigurasjonen din for å bruke Go-modeller.', "workspace.lite.black.message": - 'Du abonnerer for øyeblikket på OpenCode Black eller står på venteliste. Vennligst avslutt abonnementet først hvis du vil bytte til Go.', + "Du abonnerer for øyeblikket på OpenCode Black eller står på venteliste. Vennligst avslutt abonnementet først hvis du vil bytte til Go.", "workspace.lite.other.message": "Et annet medlem i dette arbeidsområdet abonnerer allerede på OpenCode Go. Kun ett medlem per arbeidsområde kan abonnere.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index dde32158a5..4ec3dbc641 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -543,6 +543,7 @@ export const dict = { "workspace.billing.loading": "Ładowanie...", "workspace.billing.addAction": "Dodaj", "workspace.billing.addBalance": "Doładuj saldo", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Połączono ze Stripe", "workspace.billing.manage": "Zarządzaj", "workspace.billing.enable": "Włącz rozliczenia", @@ -636,7 +637,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Wybierz "OpenCode Go" jako dostawcę w konfiguracji opencode, aby używać modeli Go.', "workspace.lite.black.message": - 'Obecnie subskrybujesz OpenCode Black lub jesteś na liście oczekujących. Jeśli chcesz przejść na Go, najpierw anuluj subskrypcję.', + "Obecnie subskrybujesz OpenCode Black lub jesteś na liście oczekujących. Jeśli chcesz przejść na Go, najpierw anuluj subskrypcję.", "workspace.lite.other.message": "Inny członek tego obszaru roboczego już subskrybuje OpenCode Go. Tylko jeden członek na obszar roboczy może subskrybować.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 4a84e91ccd..d114e188e3 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -549,6 +549,7 @@ export const dict = { "workspace.billing.loading": "Загрузка...", "workspace.billing.addAction": "Пополнить", "workspace.billing.addBalance": "Пополнить баланс", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Привязано к Stripe", "workspace.billing.manage": "Управление", "workspace.billing.enable": "Включить оплату", @@ -642,7 +643,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'Выберите "OpenCode Go" в качестве провайдера в настройках opencode для использования моделей Go.', "workspace.lite.black.message": - 'Вы подписаны на OpenCode Black или находитесь в списке ожидания. Пожалуйста, сначала отмените подписку, если хотите перейти на Go.', + "Вы подписаны на OpenCode Black или находитесь в списке ожидания. Пожалуйста, сначала отмените подписку, если хотите перейти на Go.", "workspace.lite.other.message": "Другой участник в этом рабочем пространстве уже подписан на OpenCode Go. Только один участник в рабочем пространстве может оформить подписку.", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index aabfea257d..f74c563231 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -540,6 +540,7 @@ export const dict = { "workspace.billing.loading": "กำลังโหลด...", "workspace.billing.addAction": "เพิ่ม", "workspace.billing.addBalance": "เพิ่มยอดคงเหลือ", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "เชื่อมโยงกับ Stripe", "workspace.billing.manage": "จัดการ", "workspace.billing.enable": "เปิดใช้งานการเรียกเก็บเงิน", @@ -633,7 +634,7 @@ export const dict = { "workspace.lite.subscription.selectProvider": 'เลือก "OpenCode Go" เป็นผู้ให้บริการในการตั้งค่า opencode ของคุณเพื่อใช้โมเดล Go', "workspace.lite.black.message": - 'ขณะนี้คุณสมัครสมาชิก OpenCode Black หรืออยู่ในรายการรอ โปรดยกเลิกการสมัครก่อนหากต้องการเปลี่ยนไปใช้ Go', + "ขณะนี้คุณสมัครสมาชิก OpenCode Black หรืออยู่ในรายการรอ โปรดยกเลิกการสมัครก่อนหากต้องการเปลี่ยนไปใช้ Go", "workspace.lite.other.message": "สมาชิกคนอื่นใน Workspace นี้ได้สมัคร OpenCode Go แล้ว สามารถสมัครได้เพียงหนึ่งคนต่อหนึ่ง Workspace เท่านั้น", "workspace.lite.promo.description": diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 6d6e414d11..c685bf03dd 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -545,6 +545,7 @@ export const dict = { "workspace.billing.loading": "Yükleniyor...", "workspace.billing.addAction": "Ekle", "workspace.billing.addBalance": "Bakiye Ekle", + "workspace.billing.alipay": "Alipay", "workspace.billing.linkedToStripe": "Stripe'a bağlı", "workspace.billing.manage": "Yönet", "workspace.billing.enable": "Faturalandırmayı Etkinleştir", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index ccb3a554d0..bbfc0df117 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -521,6 +521,7 @@ export const dict = { "workspace.billing.loading": "加载中...", "workspace.billing.addAction": "充值", "workspace.billing.addBalance": "充值余额", + "workspace.billing.alipay": "支付宝", "workspace.billing.linkedToStripe": "已关联 Stripe", "workspace.billing.manage": "管理", "workspace.billing.enable": "启用计费", @@ -612,8 +613,7 @@ export const dict = { "workspace.lite.subscription.useBalance": "达到使用限额后使用您的可用余额", "workspace.lite.subscription.selectProvider": "在你的 opencode 配置中选择「OpenCode Go」作为提供商,即可使用 Go 模型。", - "workspace.lite.black.message": - '您当前已订阅 OpenCode Black 或在候补名单中。如需切换到 Go,请先取消订阅。', + "workspace.lite.black.message": "您当前已订阅 OpenCode Black 或在候补名单中。如需切换到 Go,请先取消订阅。", "workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Go。每个工作区只有一名成员可以订阅。", "workspace.lite.promo.description": "OpenCode Go 是一个每月 $10 的订阅计划,提供对主流开源编码模型的稳定访问,并配备充足的使用额度。", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index bd12783e20..6a5ce0f8ad 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -522,6 +522,7 @@ export const dict = { "workspace.billing.loading": "載入中...", "workspace.billing.addAction": "儲值", "workspace.billing.addBalance": "儲值餘額", + "workspace.billing.alipay": "支付寶", "workspace.billing.linkedToStripe": "已連結 Stripe", "workspace.billing.manage": "管理", "workspace.billing.enable": "啟用帳務", @@ -613,8 +614,7 @@ export const dict = { "workspace.lite.subscription.useBalance": "達到使用限制後使用您的可用餘額", "workspace.lite.subscription.selectProvider": "在您的 opencode 設定中選擇「OpenCode Go」作為提供商,即可使用 Go 模型。", - "workspace.lite.black.message": - '您目前已訂閱 OpenCode Black 或在候補名單中。若要切換至 Go,請先取消訂閱。', + "workspace.lite.black.message": "您目前已訂閱 OpenCode Black 或在候補名單中。若要切換至 Go,請先取消訂閱。", "workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Go。每個工作區只能有一位成員訂閱。", "workspace.lite.promo.description": "OpenCode Go 是一個每月 $10 的訂閱方案,提供對主流開放原始碼編碼模型的穩定存取,並配備充足的使用額度。", diff --git a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx index db89a1c9ed..d966f38d3d 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx @@ -3,7 +3,7 @@ import { createMemo, Match, Show, Switch, createEffect } from "solid-js" import { createStore } from "solid-js/store" import { Billing } from "@opencode-ai/console-core/billing.js" import { withActor } from "~/context/auth.withActor" -import { IconCreditCard, IconStripe } from "~/component/icon" +import { IconAlipay, IconCreditCard, IconStripe } from "~/component/icon" import styles from "./billing-section.module.css" import { createCheckoutUrl, formatBalance, queryBillingInfo } from "../../common" import { useI18n } from "~/context/i18n" @@ -205,6 +205,9 @@ export function BillingSection() { <Match when={billingInfo()?.paymentMethodType === "link"}> <IconStripe style={{ width: "24px", height: "24px" }} /> </Match> + <Match when={billingInfo()?.paymentMethodType === "alipay"}> + <IconAlipay style={{ width: "24px", height: "24px" }} /> + </Match> </Switch> </div> <div data-slot="card-details"> @@ -218,6 +221,9 @@ export function BillingSection() { <Match when={billingInfo()?.paymentMethodType === "link"}> <span data-slot="type">{i18n.t("workspace.billing.linkedToStripe")}</span> </Match> + <Match when={billingInfo()?.paymentMethodType === "alipay"}> + <span data-slot="type">{i18n.t("workspace.billing.alipay")}</span> + </Match> </Switch> </div> <button diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index fcf238a353..7107f93989 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -265,7 +265,7 @@ export namespace Billing { customer_email: email!, }), currency: "usd", - payment_method_types: ["card"], + payment_method_types: ["card", "alipay"], tax_id_collection: { enabled: true, }, From 9e6c4a01aab4f774a295d6b9d4f2f30d7bac05a0 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 03:40:10 -0400 Subject: [PATCH 099/200] zen: add alipay for adding balance --- packages/console/core/src/billing.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index 7107f93989..a1078052c3 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -212,13 +212,18 @@ export namespace Billing { invoice_creation: { enabled: true, }, - payment_intent_data: { - setup_future_usage: "on_session", - }, - payment_method_types: ["card"], - payment_method_data: { - allow_redisplay: "always", + payment_method_options: { + alipay: { + setup_future_usage: "off_session", + }, + card: { + setup_future_usage: "on_session", + }, }, + payment_method_types: ["card", "alipay"], + //payment_method_data: { + // allow_redisplay: "always", + //}, tax_id_collection: { enabled: true, }, From 0b0ad5de99096bd531af7878e198d3d28c0e61f8 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 04:10:44 -0400 Subject: [PATCH 100/200] zen: update discount copy on lander --- packages/console/app/src/i18n/ar.ts | 19 +++++++++--------- packages/console/app/src/i18n/br.ts | 19 +++++++++--------- packages/console/app/src/i18n/da.ts | 19 +++++++++--------- packages/console/app/src/i18n/de.ts | 19 +++++++++--------- packages/console/app/src/i18n/en.ts | 19 +++++++++--------- packages/console/app/src/i18n/es.ts | 19 +++++++++--------- packages/console/app/src/i18n/fr.ts | 19 +++++++++--------- packages/console/app/src/i18n/it.ts | 19 +++++++++--------- packages/console/app/src/i18n/ja.ts | 19 +++++++++--------- packages/console/app/src/i18n/ko.ts | 19 +++++++++--------- packages/console/app/src/i18n/no.ts | 19 +++++++++--------- packages/console/app/src/i18n/pl.ts | 19 +++++++++--------- packages/console/app/src/i18n/ru.ts | 19 +++++++++--------- packages/console/app/src/i18n/th.ts | 19 +++++++++--------- packages/console/app/src/i18n/tr.ts | 21 +++++++++++--------- packages/console/app/src/i18n/zh.ts | 19 +++++++++--------- packages/console/app/src/i18n/zht.ts | 19 +++++++++--------- packages/console/app/src/routes/go/index.css | 13 +++++++++++- packages/console/app/src/routes/go/index.tsx | 9 ++++++++- 19 files changed, 192 insertions(+), 155 deletions(-) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 081535a3a4..ef163fa269 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -249,7 +249,7 @@ export const dict = { "go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع", "go.meta.description": - "Go هو اشتراك بقيمة 10 دولارات شهريًا مع حدود سخية تبلغ 5 ساعات للطلبات لنماذج GLM-5 وKimi K2.5 وMiniMax M2.5.", + "يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5 و Kimi K2.5 و MiniMax M2.5.", "go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع", "go.hero.body": "يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.", @@ -258,7 +258,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "اشترك في Go", "go.cta.price": "$10/شهر", - "go.pricing.body": "استخدمه مع أي وكيل. اشحن الرصيد إذا لزم الأمر. ألغِ في أي وقت.", + "go.cta.promo": "$5 للشهر الأول", + "go.pricing.body": "استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.", "go.graph.free": "مجاني", "go.graph.freePill": "Big Pickle ونماذج مجانية", "go.graph.go": "Go", @@ -290,20 +291,20 @@ export const dict = { "go.testimonials.frank.quote": "أتمنى لو كنت لا أزال في Nvidia.", "go.problem.title": "ما المشكلة التي يحلها Go؟", "go.problem.body": - "نحن نركز على جلب تجربة OpenCode لأكبر عدد ممكن من الناس. OpenCode Go هو اشتراك منخفض التكلفة (10 دولارات شهريًا) مصمم لجلب البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر.", + "نحن نركز على تقديم تجربة OpenCode لأكبر عدد ممكن من الناس. OpenCode Go هو اشتراك منخفض التكلفة: $5 للشهر الأول، ثم $10/شهر. يوفر حدودا سخية ووصولا موثوقا إلى نماذج المصدر المفتوح الأكثر قدرة.", "go.problem.subtitle": " ", "go.problem.item1": "أسعار اشتراك منخفضة التكلفة", "go.problem.item2": "حدود سخية ووصول موثوق", "go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين", "go.problem.item4": "يتضمن GLM-5 وKimi K2.5 وMiniMax M2.5", "go.how.title": "كيف يعمل Go", - "go.how.body": "Go هو اشتراك بقيمة 10 دولارات شهريًا يمكنك استخدامه مع OpenCode أو أي وكيل.", + "go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.", "go.how.step1.title": "أنشئ حسابًا", "go.how.step1.beforeLink": "اتبع", "go.how.step1.link": "تعليمات الإعداد", "go.how.step2.title": "اشترك في Go", - "go.how.step2.link": "$10/شهر", - "go.how.step2.afterLink": "مع حدود سخية", + "go.how.step2.link": "$5 للشهر الأول", + "go.how.step2.afterLink": "ثم $10/شهر مع حدود سخية", "go.how.step3.title": "ابدأ البرمجة", "go.how.step3.body": "مع وصول موثوق لنماذج مفتوحة المصدر", "go.privacy.title": "خصوصيتك مهمة بالنسبة لنا", @@ -319,11 +320,11 @@ export const dict = { "go.faq.a2": "يتضمن Go نماذج GLM-5 وKimi K2.5 وMiniMax M2.5، مع حدود سخية ووصول موثوق.", "go.faq.q3": "هل Go هو نفسه Zen؟", "go.faq.a3": - "لا. Zen هو نظام الدفع حسب الاستخدام، بينما Go هو اشتراك بقيمة 10 دولارات شهريًا مع حدود سخية ووصول موثوق لنماذج مفتوحة المصدر GLM-5 وKimi K2.5 وMiniMax M2.5.", + "لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5 و Kimi K2.5 و MiniMax M2.5.", "go.faq.q4": "كم تكلفة Go؟", "go.faq.a4.p1.beforePricing": "تكلفة Go", - "go.faq.a4.p1.pricingLink": "$10/شهر", - "go.faq.a4.p1.afterPricing": "مع حدود سخية.", + "go.faq.a4.p1.pricingLink": "$5 للشهر الأول", + "go.faq.a4.p1.afterPricing": "ثم $10/شهر مع حدود سخية.", "go.faq.a4.p2.beforeAccount": "يمكنك إدارة اشتراكك في", "go.faq.a4.p2.accountLink": "حسابك", "go.faq.a4.p3": "ألغِ في أي وقت.", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 96f40499e7..d7d2a48d47 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos", "go.meta.description": - "O Go é uma assinatura de $10/mês com limites generosos de 5 horas de requisição para GLM-5, Kimi K2.5 e MiniMax M2.5.", + "O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5, Kimi K2.5 e MiniMax M2.5.", "go.hero.title": "Modelos de codificação de baixo custo para todos", "go.hero.body": "O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.", @@ -262,7 +262,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Assinar o Go", "go.cta.price": "$10/mês", - "go.pricing.body": "Use com qualquer agente. Recarregue crédito se necessário. Cancele a qualquer momento.", + "go.cta.promo": "$5 no primeiro mês", + "go.pricing.body": "Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.", "go.graph.free": "Grátis", "go.graph.freePill": "Big Pickle e modelos gratuitos", "go.graph.go": "Go", @@ -295,20 +296,20 @@ export const dict = { "go.testimonials.frank.quote": "Eu queria ainda estar na Nvidia.", "go.problem.title": "Que problema o Go resolve?", "go.problem.body": - "Estamos focados em levar a experiência OpenCode para o maior número possível de pessoas. OpenCode Go é uma assinatura de baixo custo ($10/mês) projetada para levar a codificação com agentes para programadores em todo o mundo. Fornece limites generosos e acesso confiável aos modelos de código aberto mais capazes.", + "Estamos focados em levar a experiência do OpenCode para o maior número de pessoas possível. OpenCode Go é uma assinatura de baixo custo: $5 no primeiro mês, depois $10/mês. Oferece limites generosos e acesso confiável aos modelos open source mais capazes.", "go.problem.subtitle": " ", "go.problem.item1": "Preço de assinatura de baixo custo", "go.problem.item2": "Limites generosos e acesso confiável", "go.problem.item3": "Feito para o maior número possível de programadores", "go.problem.item4": "Inclui GLM-5, Kimi K2.5 e MiniMax M2.5", "go.how.title": "Como o Go funciona", - "go.how.body": "Go é uma assinatura de $10/mês que você pode usar com OpenCode ou qualquer agente.", + "go.how.body": "O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.", "go.how.step1.title": "Crie uma conta", "go.how.step1.beforeLink": "siga as", "go.how.step1.link": "instruções de configuração", "go.how.step2.title": "Assinar o Go", - "go.how.step2.link": "$10/mês", - "go.how.step2.afterLink": "com limites generosos", + "go.how.step2.link": "$5 no primeiro mês", + "go.how.step2.afterLink": "depois $10/mês com limites generosos", "go.how.step3.title": "Comece a codificar", "go.how.step3.body": "com acesso confiável a modelos de código aberto", "go.privacy.title": "Sua privacidade é importante para nós", @@ -325,11 +326,11 @@ export const dict = { "go.faq.a2": "Go inclui GLM-5, Kimi K2.5 e MiniMax M2.5, com limites generosos e acesso confiável.", "go.faq.q3": "O Go é o mesmo que o Zen?", "go.faq.a3": - "Não. O Zen é pago por uso (pay-as-you-go), enquanto o Go é uma assinatura de $10/mês com limites generosos e acesso confiável aos modelos de código aberto GLM-5, Kimi K2.5 e MiniMax M2.5.", + "Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5, Kimi K2.5 e MiniMax M2.5.", "go.faq.q4": "Quanto custa o Go?", "go.faq.a4.p1.beforePricing": "O Go custa", - "go.faq.a4.p1.pricingLink": "$10/mês", - "go.faq.a4.p1.afterPricing": "com limites generosos.", + "go.faq.a4.p1.pricingLink": "$5 no primeiro mês", + "go.faq.a4.p1.afterPricing": "depois $10/mês com limites generosos.", "go.faq.a4.p2.beforeAccount": "Você pode gerenciar sua assinatura em sua", "go.faq.a4.p2.accountLink": "conta", "go.faq.a4.p3": "Cancele a qualquer momento.", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 7ed1114857..541557f7fd 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle", "go.meta.description": - "Go er et abonnement til $10/måned med generøse grænser på 5 timers forespørgsler for GLM-5, Kimi K2.5 og MiniMax M2.5.", + "Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5, Kimi K2.5 og MiniMax M2.5.", "go.hero.title": "Kodningsmodeller til lav pris for alle", "go.hero.body": "Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.", @@ -260,7 +260,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Abonner på Go", "go.cta.price": "$10/måned", - "go.pricing.body": "Brug med enhver agent. Genopfyld kredit om nødvendigt. Annuller til enhver tid.", + "go.cta.promo": "$5 første måned", + "go.pricing.body": "Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", @@ -292,20 +293,20 @@ export const dict = { "go.testimonials.frank.quote": "Jeg ville ønske, jeg stadig var hos Nvidia.", "go.problem.title": "Hvilket problem løser Go?", "go.problem.body": - "Vi fokuserer på at bringe OpenCode-oplevelsen til så mange mennesker som muligt. OpenCode Go er et lavprisabonnement ($10/måned) designet til at bringe agentisk kodning til programmører over hele verden. Det giver generøse grænser og pålidelig adgang til de mest kapable open source-modeller.", + "Vi fokuserer på at bringe OpenCode-oplevelsen ud til så mange som muligt. OpenCode Go er et lavprisabonnement: $5 for den første måned, derefter $10/måned. Det giver generøse grænser og pålidelig adgang til de mest kapable open source-modeller.", "go.problem.subtitle": " ", "go.problem.item1": "Lavpris abonnementspriser", "go.problem.item2": "Generøse grænser og pålidelig adgang", "go.problem.item3": "Bygget til så mange programmører som muligt", "go.problem.item4": "Inkluderer GLM-5, Kimi K2.5 og MiniMax M2.5", "go.how.title": "Hvordan Go virker", - "go.how.body": "Go er et abonnement til $10/måned, som du kan bruge med OpenCode eller enhver anden agent.", + "go.how.body": "Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.", "go.how.step1.title": "Opret en konto", "go.how.step1.beforeLink": "følg", "go.how.step1.link": "opsætningsinstruktionerne", "go.how.step2.title": "Abonner på Go", - "go.how.step2.link": "$10/måned", - "go.how.step2.afterLink": "med generøse grænser", + "go.how.step2.link": "$5 første måned", + "go.how.step2.afterLink": "derefter $10/måned med generøse grænser", "go.how.step3.title": "Start kodning", "go.how.step3.body": "med pålidelig adgang til open source-modeller", "go.privacy.title": "Dit privatliv er vigtigt for os", @@ -322,11 +323,11 @@ export const dict = { "go.faq.a2": "Go inkluderer GLM-5, Kimi K2.5 og MiniMax M2.5, med generøse grænser og pålidelig adgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nej. Zen er pay-as-you-go, mens Go er et abonnement til $10/måned med generøse grænser og pålidelig adgang til open source-modellerne GLM-5, Kimi K2.5 og MiniMax M2.5.", + "Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5, Kimi K2.5 og MiniMax M2.5.", "go.faq.q4": "Hvad koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", - "go.faq.a4.p1.pricingLink": "$10/måned", - "go.faq.a4.p1.afterPricing": "med generøse grænser.", + "go.faq.a4.p1.pricingLink": "$5 første måned", + "go.faq.a4.p1.afterPricing": "derefter $10/måned med generøse grænser.", "go.faq.a4.p2.beforeAccount": "Du kan administrere dit abonnement i din", "go.faq.a4.p2.accountLink": "konto", "go.faq.a4.p3": "Annuller til enhver tid.", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index bd81c9bf80..4097b4cbd1 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle", "go.meta.description": - "Go ist ein Abonnement für $10/Monat mit großzügigen 5-Stunden-Limits für GLM-5, Kimi K2.5 und MiniMax M2.5.", + "Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5, Kimi K2.5 und MiniMax M2.5.", "go.hero.title": "Kostengünstige Coding-Modelle für alle", "go.hero.body": "Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.", @@ -262,7 +262,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Go abonnieren", "go.cta.price": "$10/Monat", - "go.pricing.body": "Nutzung mit jedem Agenten. Guthaben bei Bedarf aufladen. Jederzeit kündbar.", + "go.cta.promo": "$5 im ersten Monat", + "go.pricing.body": "Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.", "go.graph.free": "Kostenlos", "go.graph.freePill": "Big Pickle und kostenlose Modelle", "go.graph.go": "Go", @@ -294,20 +295,20 @@ export const dict = { "go.testimonials.frank.quote": "Ich wünschte, ich wäre noch bei Nvidia.", "go.problem.title": "Welches Problem löst Go?", "go.problem.body": - "Wir konzentrieren uns darauf, die OpenCode-Erfahrung so vielen Menschen wie möglich zugänglich zu machen. OpenCode Go ist ein kostengünstiges ($10/Monat) Abonnement, das entwickelt wurde, um Agentic Coding zu Programmierern auf der ganzen Welt zu bringen. Es bietet großzügige Limits und zuverlässigen Zugang zu den leistungsfähigsten Open-Source-Modellen.", + "Wir konzentrieren uns darauf, die OpenCode-Erfahrung so vielen Menschen wie möglich zugänglich zu machen. OpenCode Go ist ein kostengünstiges Abonnement: $5 im ersten Monat, danach $10/Monat. Es bietet großzügige Limits und zuverlässigen Zugang zu den leistungsfähigsten Open-Source-Modellen.", "go.problem.subtitle": " ", "go.problem.item1": "Kostengünstiges Abonnement", "go.problem.item2": "Großzügige Limits und zuverlässiger Zugang", "go.problem.item3": "Für so viele Programmierer wie möglich gebaut", "go.problem.item4": "Beinhaltet GLM-5, Kimi K2.5 und MiniMax M2.5", "go.how.title": "Wie Go funktioniert", - "go.how.body": "Go ist ein Abonnement für $10/Monat, das du mit OpenCode oder jedem anderen Agenten nutzen kannst.", + "go.how.body": "Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.", "go.how.step1.title": "Konto erstellen", "go.how.step1.beforeLink": "folge den", "go.how.step1.link": "Einrichtungsanweisungen", "go.how.step2.title": "Go abonnieren", - "go.how.step2.link": "$10/Monat", - "go.how.step2.afterLink": "mit großzügigen Limits", + "go.how.step2.link": "$5 im ersten Monat", + "go.how.step2.afterLink": "danach $10/Monat mit großzügigen Limits", "go.how.step3.title": "Loslegen mit Coding", "go.how.step3.body": "mit zuverlässigem Zugang zu Open-Source-Modellen", "go.privacy.title": "Deine Privatsphäre ist uns wichtig", @@ -324,11 +325,11 @@ export const dict = { "go.faq.a2": "Go beinhaltet GLM-5, Kimi K2.5 und MiniMax M2.5, mit großzügigen Limits und zuverlässigem Zugang.", "go.faq.q3": "Ist Go dasselbe wie Zen?", "go.faq.a3": - "Nein. Zen ist Pay-as-you-go, während Go ein Abonnement für $10/Monat mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5, Kimi K2.5 und MiniMax M2.5 ist.", + "Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5, Kimi K2.5 und MiniMax M2.5.", "go.faq.q4": "Wie viel kostet Go?", "go.faq.a4.p1.beforePricing": "Go kostet", - "go.faq.a4.p1.pricingLink": "$10/Monat", - "go.faq.a4.p1.afterPricing": "mit großzügigen Limits.", + "go.faq.a4.p1.pricingLink": "$5 im ersten Monat", + "go.faq.a4.p1.afterPricing": "danach $10/Monat mit großzügigen Limits.", "go.faq.a4.p2.beforeAccount": "Du kannst dein Abonnement in deinem", "go.faq.a4.p2.accountLink": "Konto verwalten", "go.faq.a4.p3": "Jederzeit kündbar.", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 05c4643afe..2fc5d359a7 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -248,7 +248,7 @@ export const dict = { "go.title": "OpenCode Go | Low cost coding models for everyone", "go.meta.description": - "Go is a $10/month subscription with generous 5-hour request limits for GLM-5, Kimi K2.5, and MiniMax M2.5.", + "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5, Kimi K2.5, and MiniMax M2.5.", "go.hero.title": "Low cost coding models for everyone", "go.hero.body": "Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.", @@ -257,7 +257,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Subscribe to Go", "go.cta.price": "$10/month", - "go.pricing.body": "Use with any agent. Top up credit if needed. Cancel any time.", + "go.cta.promo": "$5 first month", + "go.pricing.body": "Use with any agent. $5 first month, then $10/month. Top up credit if needed. Cancel any time.", "go.graph.free": "Free", "go.graph.freePill": "Big Pickle and free models", "go.graph.go": "Go", @@ -289,20 +290,20 @@ export const dict = { "go.testimonials.frank.quote": "I wish I was still at Nvidia.", "go.problem.title": "What problem is Go solving?", "go.problem.body": - "We're focused on bringing the OpenCode experience to as many people as possible. OpenCode Go is a low cost ($10/month) subscription designed to bring agentic coding to programmers around the world. It provides generous limits and reliable access to the most capable open source models.", + "We're focused on bringing the OpenCode experience to as many people as possible. OpenCode Go is a low cost subscription: $5 for your first month, then $10/month. It provides generous limits and reliable access to the most capable open source models.", "go.problem.subtitle": " ", "go.problem.item1": "Low cost subscription pricing", "go.problem.item2": "Generous limits and reliable access", "go.problem.item3": "Built for as many programmers as possible", "go.problem.item4": "Includes GLM-5, Kimi K2.5, and MiniMax M2.5", "go.how.title": "How Go works", - "go.how.body": "Go is a $10/month subscription you can use with OpenCode or any agent.", + "go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.", "go.how.step1.title": "Create an account", "go.how.step1.beforeLink": "follow the", "go.how.step1.link": "setup instructions", "go.how.step2.title": "Subscribe to Go", - "go.how.step2.link": "$10/month", - "go.how.step2.afterLink": "with generous limits", + "go.how.step2.link": "$5 first month", + "go.how.step2.afterLink": "then $10/month with generous limits", "go.how.step3.title": "Start coding", "go.how.step3.body": "with reliable access to open-source models", "go.privacy.title": "Your privacy is important to us", @@ -319,11 +320,11 @@ export const dict = { "go.faq.a2": "Go includes GLM-5, Kimi K2.5, and MiniMax M2.5, with generous limits and reliable access.", "go.faq.q3": "Is Go the same as Zen?", "go.faq.a3": - "No. Zen is pay-as-you-go, while Go is a $10/month subscription with generous limits and reliable access to open-source models GLM-5, Kimi K2.5, and MiniMax M2.5.", + "No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5, Kimi K2.5, and MiniMax M2.5.", "go.faq.q4": "How much does Go cost?", "go.faq.a4.p1.beforePricing": "Go costs", - "go.faq.a4.p1.pricingLink": "$10/month", - "go.faq.a4.p1.afterPricing": "with generous limits.", + "go.faq.a4.p1.pricingLink": "$5 first month", + "go.faq.a4.p1.afterPricing": "then $10/month with generous limits.", "go.faq.a4.p2.beforeAccount": "You can manage your subscription in your", "go.faq.a4.p2.accountLink": "account", "go.faq.a4.p3": "Cancel any time.", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 92486987b3..3efd8e2d1d 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -254,7 +254,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de programación de bajo coste para todos", "go.meta.description": - "Go es una suscripción de 10 $/mes con generosos límites de solicitudes de 5 horas para GLM-5, Kimi K2.5 y MiniMax M2.5.", + "Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5, Kimi K2.5 y MiniMax M2.5.", "go.hero.title": "Modelos de programación de bajo coste para todos", "go.hero.body": "Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.", @@ -263,7 +263,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Suscribirse a Go", "go.cta.price": "10 $/mes", - "go.pricing.body": "Úsalo con cualquier agente. Recarga crédito si es necesario. Cancela en cualquier momento.", + "go.cta.promo": "$5 el primer mes", + "go.pricing.body": "Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle y modelos gratuitos", "go.graph.go": "Go", @@ -296,20 +297,20 @@ export const dict = { "go.testimonials.frank.quote": "Ojalá siguiera en Nvidia.", "go.problem.title": "¿Qué problema resuelve Go?", "go.problem.body": - "Estamos enfocados en llevar la experiencia de OpenCode a tanta gente como sea posible. OpenCode Go es una suscripción de bajo coste (10 $/mes) diseñada para llevar la programación agéntica a programadores de todo el mundo. Proporciona límites generosos y acceso fiable a los modelos de código abierto más capaces.", + "Nos enfocamos en llevar la experiencia de OpenCode a tantas personas como sea posible. OpenCode Go es una suscripción de bajo coste: $5 el primer mes, luego 10 $/mes. Proporciona límites generosos y acceso fiable a los modelos de código abierto más capaces.", "go.problem.subtitle": " ", "go.problem.item1": "Precios de suscripción de bajo coste", "go.problem.item2": "Límites generosos y acceso fiable", "go.problem.item3": "Creado para tantos programadores como sea posible", "go.problem.item4": "Incluye GLM-5, Kimi K2.5 y MiniMax M2.5", "go.how.title": "Cómo funciona Go", - "go.how.body": "Go es una suscripción de 10 $/mes que puedes usar con OpenCode o cualquier agente.", + "go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.", "go.how.step1.title": "Crear una cuenta", "go.how.step1.beforeLink": "sigue las", "go.how.step1.link": "instrucciones de configuración", "go.how.step2.title": "Suscribirse a Go", - "go.how.step2.link": "10 $/mes", - "go.how.step2.afterLink": "con límites generosos", + "go.how.step2.link": "$5 el primer mes", + "go.how.step2.afterLink": "luego 10 $/mes con límites generosos", "go.how.step3.title": "Empezar a programar", "go.how.step3.body": "con acceso fiable a modelos de código abierto", "go.privacy.title": "Tu privacidad es importante para nosotros", @@ -326,11 +327,11 @@ export const dict = { "go.faq.a2": "Go incluye GLM-5, Kimi K2.5 y MiniMax M2.5, con límites generosos y acceso fiable.", "go.faq.q3": "¿Es Go lo mismo que Zen?", "go.faq.a3": - "No. Zen es pago por uso, mientras que Go es una suscripción de 10 $/mes con límites generosos y acceso fiable a modelos de código abierto GLM-5, Kimi K2.5 y MiniMax M2.5.", + "No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5, Kimi K2.5 y MiniMax M2.5.", "go.faq.q4": "¿Cuánto cuesta Go?", "go.faq.a4.p1.beforePricing": "Go cuesta", - "go.faq.a4.p1.pricingLink": "10 $/mes", - "go.faq.a4.p1.afterPricing": "con límites generosos.", + "go.faq.a4.p1.pricingLink": "$5 el primer mes", + "go.faq.a4.p1.afterPricing": "luego 10 $/mes con límites generosos.", "go.faq.a4.p2.beforeAccount": "Puedes gestionar tu suscripción en tu", "go.faq.a4.p2.accountLink": "cuenta", "go.faq.a4.p3": "Cancela en cualquier momento.", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index df379fae9f..06f6dfb48f 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Modèles de code à faible coût pour tous", "go.meta.description": - "Go est un abonnement à 10 $/mois avec des limites généreuses de 5 heures de requêtes pour GLM-5, Kimi K2.5 et MiniMax M2.5.", + "Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5, Kimi K2.5 et MiniMax M2.5.", "go.hero.title": "Modèles de code à faible coût pour tous", "go.hero.body": "Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.", @@ -264,7 +264,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "S'abonner à Go", "go.cta.price": "10 $/mois", - "go.pricing.body": "Utilisez avec n'importe quel agent. Rechargez du crédit si nécessaire. Annulez à tout moment.", + "go.cta.promo": "$5 le premier mois", + "go.pricing.body": "Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.", "go.graph.free": "Gratuit", "go.graph.freePill": "Big Pickle et modèles gratuits", "go.graph.go": "Go", @@ -296,20 +297,20 @@ export const dict = { "go.testimonials.frank.quote": "J'aimerais être encore chez Nvidia.", "go.problem.title": "Quel problème Go résout-il ?", "go.problem.body": - "Nous nous concentrons sur le fait d'apporter l'expérience OpenCode à autant de personnes que possible. OpenCode Go est un abonnement à faible coût (10 $/mois) conçu pour apporter le codage agentique aux programmeurs du monde entier. Il offre des limites généreuses et un accès fiable aux modèles open source les plus capables.", + "Nous nous efforçons d'apporter l'expérience OpenCode au plus grand nombre. OpenCode Go est un abonnement à faible coût : $5 pour le premier mois, puis 10 $/mois. Il offre des limites généreuses et un accès fiable aux modèles open source les plus performants.", "go.problem.subtitle": " ", "go.problem.item1": "Prix d'abonnement bas", "go.problem.item2": "Limites généreuses et accès fiable", "go.problem.item3": "Conçu pour autant de programmeurs que possible", "go.problem.item4": "Inclut GLM-5, Kimi K2.5 et MiniMax M2.5", "go.how.title": "Comment fonctionne Go", - "go.how.body": "Go est un abonnement à 10 $/mois que vous pouvez utiliser avec OpenCode ou n'importe quel agent.", + "go.how.body": "Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.", "go.how.step1.title": "Créez un compte", "go.how.step1.beforeLink": "suivez les", "go.how.step1.link": "instructions de configuration", "go.how.step2.title": "Abonnez-vous à Go", - "go.how.step2.link": "10 $/mois", - "go.how.step2.afterLink": "avec des limites généreuses", + "go.how.step2.link": "$5 le premier mois", + "go.how.step2.afterLink": "puis 10 $/mois avec des limites généreuses", "go.how.step3.title": "Commencez à coder", "go.how.step3.body": "avec un accès fiable aux modèles open source", "go.privacy.title": "Votre vie privée est importante pour nous", @@ -326,11 +327,11 @@ export const dict = { "go.faq.a2": "Go inclut GLM-5, Kimi K2.5 et MiniMax M2.5, avec des limites généreuses et un accès fiable.", "go.faq.q3": "Est-ce que Go est la même chose que Zen ?", "go.faq.a3": - "Non. Zen est payé à l'usage (pay-as-you-go), tandis que Go est un abonnement à 10 $/mois avec des limites généreuses et un accès fiable aux modèles open source GLM-5, Kimi K2.5 et MiniMax M2.5.", + "Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5, Kimi K2.5 et MiniMax M2.5.", "go.faq.q4": "Combien coûte Go ?", "go.faq.a4.p1.beforePricing": "Go coûte", - "go.faq.a4.p1.pricingLink": "10 $/mois", - "go.faq.a4.p1.afterPricing": "avec des limites généreuses.", + "go.faq.a4.p1.pricingLink": "$5 le premier mois", + "go.faq.a4.p1.afterPricing": "puis 10 $/mois avec des limites généreuses.", "go.faq.a4.p2.beforeAccount": "Vous pouvez gérer votre abonnement dans votre", "go.faq.a4.p2.accountLink": "compte", "go.faq.a4.p3": "Annulez à tout moment.", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 24f3aa2f84..f68092cc1c 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Modelli di coding a basso costo per tutti", "go.meta.description": - "Go è un abbonamento da $10/mese con generosi limiti di richieste di 5 ore per GLM-5, Kimi K2.5 e MiniMax M2.5.", + "Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5, Kimi K2.5 e MiniMax M2.5.", "go.hero.title": "Modelli di coding a basso costo per tutti", "go.hero.body": "Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.", @@ -260,7 +260,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Abbonati a Go", "go.cta.price": "$10/mese", - "go.pricing.body": "Usa con qualsiasi agente. Ricarica credito se necessario. Annulla in qualsiasi momento.", + "go.cta.promo": "$5 il primo mese", + "go.pricing.body": "Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle e modelli gratuiti", "go.graph.go": "Go", @@ -292,20 +293,20 @@ export const dict = { "go.testimonials.frank.quote": "Vorrei essere ancora a Nvidia.", "go.problem.title": "Quale problema risolve Go?", "go.problem.body": - "Ci concentriamo nel portare l'esperienza OpenCode a quante più persone possibile. OpenCode Go è un abbonamento a basso costo ($10/mese) progettato per portare il coding agentico ai programmatori di tutto il mondo. Fornisce limiti generosi e accesso affidabile ai modelli open source più capaci.", + "Ci concentriamo nel portare l'esperienza OpenCode a quante più persone possibile. OpenCode Go è un abbonamento a basso costo: $5 il primo mese, poi $10/mese. Offre limiti generosi e accesso affidabile ai modelli open source più capaci.", "go.problem.subtitle": " ", "go.problem.item1": "Prezzo di abbonamento a basso costo", "go.problem.item2": "Limiti generosi e accesso affidabile", "go.problem.item3": "Costruito per il maggior numero possibile di programmatori", "go.problem.item4": "Include GLM-5, Kimi K2.5 e MiniMax M2.5", "go.how.title": "Come funziona Go", - "go.how.body": "Go è un abbonamento da $10/mese che puoi usare con OpenCode o qualsiasi agente.", + "go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.", "go.how.step1.title": "Crea un account", "go.how.step1.beforeLink": "segui le", "go.how.step1.link": "istruzioni di configurazione", "go.how.step2.title": "Abbonati a Go", - "go.how.step2.link": "$10/mese", - "go.how.step2.afterLink": "con limiti generosi", + "go.how.step2.link": "$5 il primo mese", + "go.how.step2.afterLink": "poi $10/mese con limiti generosi", "go.how.step3.title": "Inizia a programmare", "go.how.step3.body": "con accesso affidabile ai modelli open source", "go.privacy.title": "La tua privacy è importante per noi", @@ -322,11 +323,11 @@ export const dict = { "go.faq.a2": "Go include GLM-5, Kimi K2.5 e MiniMax M2.5, con limiti generosi e accesso affidabile.", "go.faq.q3": "Go è lo stesso di Zen?", "go.faq.a3": - "No. Zen è a consumo, mentre Go è un abbonamento da $10/mese con limiti generosi e accesso affidabile ai modelli open source GLM-5, Kimi K2.5 e MiniMax M2.5.", + "No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5, Kimi K2.5 e MiniMax M2.5.", "go.faq.q4": "Quanto costa Go?", "go.faq.a4.p1.beforePricing": "Go costa", - "go.faq.a4.p1.pricingLink": "$10/mese", - "go.faq.a4.p1.afterPricing": "con limiti generosi.", + "go.faq.a4.p1.pricingLink": "$5 il primo mese", + "go.faq.a4.p1.afterPricing": "poi $10/mese con limiti generosi.", "go.faq.a4.p2.beforeAccount": "Puoi gestire il tuo abbonamento nel tuo", "go.faq.a4.p2.accountLink": "account", "go.faq.a4.p3": "Annulla in qualsiasi momento.", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index f11f5052bf..34e5ebf8bd 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル", "go.meta.description": - "Goは、GLM-5、Kimi K2.5、MiniMax M2.5を5時間ごとの十分なリクエスト制限で利用できる月額$10のサブスクリプションです。", + "Goは最初の月$5、その後$10/月で、GLM-5、Kimi K2.5、MiniMax M2.5に対して5時間のゆとりあるリクエスト上限があります。", "go.hero.title": "すべての人のための低価格なコーディングモデル", "go.hero.body": "Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。", @@ -259,7 +259,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Goを購読する", "go.cta.price": "$10/月", - "go.pricing.body": "任意のエージェントで利用可能。必要に応じてクレジットを追加。いつでもキャンセル可能。", + "go.cta.promo": "初月 $5", + "go.pricing.body": "どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。", "go.graph.free": "無料", "go.graph.freePill": "Big Pickleと無料モデル", "go.graph.go": "Go", @@ -292,20 +293,20 @@ export const dict = { "go.testimonials.frank.quote": "まだNvidiaにいられたらよかったのに。", "go.problem.title": "Goはどのような問題を解決していますか?", "go.problem.body": - "私たちは、OpenCodeの体験をできるだけ多くの人々に届けることに注力しています。OpenCode Goは、世界中のプログラマーにエージェント型コーディングをもたらすために設計された低価格($10/月)のサブスクリプションです。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供します。", + "私たちはOpenCodeの体験をできるだけ多くの人に届けることに注力しています。OpenCode Goは低価格のサブスクリプションで、最初の月は$5、その後は$10/月です。ゆとりある上限と、最も高性能なオープンソースモデルへの信頼できるアクセスを提供します。", "go.problem.subtitle": " ", "go.problem.item1": "低価格なサブスクリプション料金", "go.problem.item2": "十分な制限と安定したアクセス", "go.problem.item3": "できるだけ多くのプログラマーのために構築", "go.problem.item4": "GLM-5、Kimi K2.5、MiniMax M2.5を含む", "go.how.title": "Goの仕組み", - "go.how.body": "Goは、OpenCodeまたは任意のエージェントで使用できる月額$10のサブスクリプションです。", + "go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。", "go.how.step1.title": "アカウントを作成", "go.how.step1.beforeLink": "", "go.how.step1.link": "セットアップ手順はこちら", "go.how.step2.title": "Goを購読する", - "go.how.step2.link": "$10/月", - "go.how.step2.afterLink": "(十分な制限付き)", + "go.how.step2.link": "最初の月$5", + "go.how.step2.afterLink": "その後$10/月、ゆとりある上限付き", "go.how.step3.title": "コーディングを開始", "go.how.step3.body": "オープンソースモデルへの安定したアクセスで", "go.privacy.title": "あなたのプライバシーは私たちにとって重要です", @@ -322,11 +323,11 @@ export const dict = { "go.faq.a2": "Goには、GLM-5、Kimi K2.5、MiniMax M2.5が含まれており、十分な制限と安定したアクセスが提供されます。", "go.faq.q3": "GoはZenと同じですか?", "go.faq.a3": - "いいえ。Zenは従量課金制ですが、Goは月額$10のサブスクリプションで、GLM-5、Kimi K2.5、MiniMax M2.5といったオープンソースモデルへの十分な制限と安定したアクセスを提供します。", + "いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5、Kimi K2.5、MiniMax M2.5のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。", "go.faq.q4": "Goの料金は?", "go.faq.a4.p1.beforePricing": "Goは", - "go.faq.a4.p1.pricingLink": "月額$10", - "go.faq.a4.p1.afterPricing": "で、十分な制限が含まれます。", + "go.faq.a4.p1.pricingLink": "最初の月$5", + "go.faq.a4.p1.afterPricing": "その後$10/月、ゆとりある上限付き。", "go.faq.a4.p2.beforeAccount": "管理画面:", "go.faq.a4.p2.accountLink": "アカウント", "go.faq.a4.p3": "いつでもキャンセル可能です。", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index fe33bf5457..3ebcbfc9f5 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -247,7 +247,7 @@ export const dict = { "go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델", "go.meta.description": - "Go는 GLM-5, Kimi K2.5, MiniMax M2.5에 대해 넉넉한 5시간 요청 한도를 제공하는 월 $10 구독입니다.", + "Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5, Kimi K2.5, MiniMax M2.5에 대해 넉넉한 5시간 요청 한도를 제공합니다.", "go.hero.title": "모두를 위한 저비용 코딩 모델", "go.hero.body": "Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.", @@ -256,7 +256,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Go 구독하기", "go.cta.price": "$10/월", - "go.pricing.body": "모든 에이전트와 함께 사용하세요. 필요 시 크레딧을 충전하세요. 언제든지 취소 가능.", + "go.cta.promo": "첫 달 $5", + "go.pricing.body": "어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.", "go.graph.free": "무료", "go.graph.freePill": "Big Pickle 및 무료 모델", "go.graph.go": "Go", @@ -289,20 +290,20 @@ export const dict = { "go.testimonials.frank.quote": "아직 Nvidia에 있었으면 좋았을 텐데요.", "go.problem.title": "Go는 어떤 문제를 해결하나요?", "go.problem.body": - "우리는 가능한 한 많은 사람들에게 OpenCode 경험을 제공하는 데 집중하고 있습니다. OpenCode Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공하기 위해 설계된 저렴한(월 $10) 구독입니다. 가장 유능한 오픈 소스 모델에 대해 넉넉한 한도와 안정적인 액세스를 제공합니다.", + "우리는 가능한 많은 사람들에게 OpenCode 경험을 제공하는 데 집중하고 있습니다. OpenCode Go는 저렴한 구독 서비스로, 첫 달 $5, 이후 $10/월입니다. 넉넉한 한도와 가장 뛰어난 오픈 소스 모델에 대한 안정적인 액세스를 제공합니다.", "go.problem.subtitle": " ", "go.problem.item1": "저렴한 구독 가격", "go.problem.item2": "넉넉한 한도와 안정적인 액세스", "go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨", "go.problem.item4": "GLM-5, Kimi K2.5, MiniMax M2.5 포함", "go.how.title": "Go 작동 방식", - "go.how.body": "Go는 OpenCode 또는 다른 어떤 에이전트와도 사용할 수 있는 월 $10 구독입니다.", + "go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.", "go.how.step1.title": "계정 생성", "go.how.step1.beforeLink": "", "go.how.step1.link": "설정 지침을 따르세요", "go.how.step2.title": "Go 구독", - "go.how.step2.link": "$10/월", - "go.how.step2.afterLink": "(넉넉한 한도 포함)", + "go.how.step2.link": "첫 달 $5", + "go.how.step2.afterLink": "이후 $10/월, 넉넉한 한도 포함", "go.how.step3.title": "코딩 시작", "go.how.step3.body": "오픈 소스 모델에 대한 안정적인 액세스와 함께", "go.privacy.title": "귀하의 프라이버시는 우리에게 중요합니다", @@ -318,11 +319,11 @@ export const dict = { "go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 GLM-5, Kimi K2.5, MiniMax M2.5가 포함됩니다.", "go.faq.q3": "Go는 Zen과 같은가요?", "go.faq.a3": - "아니요. Zen은 사용한 만큼 지불(pay-as-you-go)하는 방식인 반면, Go는 월 $10 구독으로 오픈 소스 모델인 GLM-5, Kimi K2.5, MiniMax M2.5에 대해 넉넉한 한도와 안정적인 액세스를 제공합니다.", + "아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5, Kimi K2.5, MiniMax M2.5 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.", "go.faq.q4": "Go 비용은 얼마인가요?", "go.faq.a4.p1.beforePricing": "Go 비용은", - "go.faq.a4.p1.pricingLink": "$10/월", - "go.faq.a4.p1.afterPricing": "이며 넉넉한 한도를 제공합니다.", + "go.faq.a4.p1.pricingLink": "첫 달 $5", + "go.faq.a4.p1.afterPricing": "이후 $10/월, 넉넉한 한도 포함.", "go.faq.a4.p2.beforeAccount": "구독 관리는 다음에서 가능합니다:", "go.faq.a4.p2.accountLink": "계정", "go.faq.a4.p3": "언제든지 취소할 수 있습니다.", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index af2a8d59f3..f3ee28c061 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Rimelige kodemodeller for alle", "go.meta.description": - "Go er et abonnement til $10/måned med rause grenser på 5 timer for GLM-5, Kimi K2.5 og MiniMax M2.5.", + "Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5, Kimi K2.5 og MiniMax M2.5.", "go.hero.title": "Rimelige kodemodeller for alle", "go.hero.body": "Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.", @@ -260,7 +260,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Abonner på Go", "go.cta.price": "$10/måned", - "go.pricing.body": "Bruk med hvilken som helst agent. Fyll på kreditt om nødvendig. Avslutt når som helst.", + "go.cta.promo": "$5 første måned", + "go.pricing.body": "Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", @@ -292,20 +293,20 @@ export const dict = { "go.testimonials.frank.quote": "Jeg skulle ønske jeg fortsatt var hos Nvidia.", "go.problem.title": "Hvilket problem løser Go?", "go.problem.body": - "Vi fokuserer på å bringe OpenCode-opplevelsen til så mange mennesker som mulig. OpenCode Go er et rimelig ($10/måned) abonnement designet for å bringe agent-koding til programmerere over hele verden. Det gir rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene.", + "Vi fokuserer på å bringe OpenCode-opplevelsen til så mange som mulig. OpenCode Go er et rimelig abonnement: $5 for den første måneden, deretter $10/måned. Det gir sjenerøse grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene.", "go.problem.subtitle": " ", "go.problem.item1": "Rimelig abonnementspris", "go.problem.item2": "Rause grenser og pålitelig tilgang", "go.problem.item3": "Bygget for så mange programmerere som mulig", "go.problem.item4": "Inkluderer GLM-5, Kimi K2.5 og MiniMax M2.5", "go.how.title": "Hvordan Go fungerer", - "go.how.body": "Go er et abonnement til $10/måned som du kan bruke med OpenCode eller hvilken som helst agent.", + "go.how.body": "Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.", "go.how.step1.title": "Opprett en konto", "go.how.step1.beforeLink": "følg", "go.how.step1.link": "oppsettsinstruksjonene", "go.how.step2.title": "Abonner på Go", - "go.how.step2.link": "$10/måned", - "go.how.step2.afterLink": "med rause grenser", + "go.how.step2.link": "$5 første måned", + "go.how.step2.afterLink": "deretter $10/måned med sjenerøse grenser", "go.how.step3.title": "Begynn å kode", "go.how.step3.body": "med pålitelig tilgang til åpen kildekode-modeller", "go.privacy.title": "Personvernet ditt er viktig for oss", @@ -322,11 +323,11 @@ export const dict = { "go.faq.a2": "Go inkluderer GLM-5, Kimi K2.5 og MiniMax M2.5, med rause grenser og pålitelig tilgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nei. Zen er pay-as-you-go, mens Go er et abonnement til $10/måned med rause grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5, Kimi K2.5 og MiniMax M2.5.", + "Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5, Kimi K2.5 og MiniMax M2.5.", "go.faq.q4": "Hva koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", - "go.faq.a4.p1.pricingLink": "$10/måned", - "go.faq.a4.p1.afterPricing": "med rause grenser.", + "go.faq.a4.p1.pricingLink": "$5 første måned", + "go.faq.a4.p1.afterPricing": "deretter $10/måned med sjenerøse grenser.", "go.faq.a4.p2.beforeAccount": "Du kan administrere abonnementet ditt i din", "go.faq.a4.p2.accountLink": "konto", "go.faq.a4.p3": "Avslutt når som helst.", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 4ec3dbc641..8f1b00f2df 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -252,7 +252,7 @@ export const dict = { "go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego", "go.meta.description": - "Go to subskrypcja za $10/miesiąc z hojnymi 5-godzinnymi limitami zapytań dla GLM-5, Kimi K2.5 i MiniMax M2.5.", + "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5, Kimi K2.5 i MiniMax M2.5.", "go.hero.title": "Niskokosztowe modele do kodowania dla każdego", "go.hero.body": "Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.", @@ -261,7 +261,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Zasubskrybuj Go", "go.cta.price": "$10/miesiąc", - "go.pricing.body": "Używaj z dowolnym agentem. Doładuj środki w razie potrzeby. Anuluj w dowolnym momencie.", + "go.cta.promo": "$5 pierwszy miesiąc", + "go.pricing.body": "Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.", "go.graph.free": "Darmowe", "go.graph.freePill": "Big Pickle i darmowe modele", "go.graph.go": "Go", @@ -293,20 +294,20 @@ export const dict = { "go.testimonials.frank.quote": "Chciałbym wciąż być w Nvidia.", "go.problem.title": "Jaki problem rozwiązuje Go?", "go.problem.body": - "Skupiamy się na dostarczeniu doświadczenia OpenCode jak największej liczbie osób. OpenCode Go to niskokosztowa ($10/miesiąc) subskrypcja zaprojektowana, aby udostępnić programowanie z agentami programistom na całym świecie. Zapewnia hojne limity i niezawodny dostęp do najzdolniejszych modeli open source.", + "Skupiamy się na udostępnieniu doświadczenia OpenCode jak największej liczbie osób. OpenCode Go to tania subskrypcja: $5 za pierwszy miesiąc, potem $10/miesiąc. Zapewnia hojne limity i niezawodny dostęp do najbardziej wydajnych modeli open source.", "go.problem.subtitle": " ", "go.problem.item1": "Niskokosztowa cena subskrypcji", "go.problem.item2": "Hojne limity i niezawodny dostęp", "go.problem.item3": "Stworzony dla jak największej liczby programistów", "go.problem.item4": "Zawiera GLM-5, Kimi K2.5 i MiniMax M2.5", "go.how.title": "Jak działa Go", - "go.how.body": "Go to subskrypcja za $10/miesiąc, której możesz używać z OpenCode lub dowolnym agentem.", + "go.how.body": "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.", "go.how.step1.title": "Załóż konto", "go.how.step1.beforeLink": "postępuj zgodnie z", "go.how.step1.link": "instrukcją konfiguracji", "go.how.step2.title": "Zasubskrybuj Go", - "go.how.step2.link": "$10/miesiąc", - "go.how.step2.afterLink": "z hojnymi limitami", + "go.how.step2.link": "$5 za pierwszy miesiąc", + "go.how.step2.afterLink": "potem $10/miesiąc z hojnymi limitami", "go.how.step3.title": "Zacznij kodować", "go.how.step3.body": "z niezawodnym dostępem do modeli open source", "go.privacy.title": "Twoja prywatność jest dla nas ważna", @@ -323,11 +324,11 @@ export const dict = { "go.faq.a2": "Go zawiera GLM-5, Kimi K2.5 i MiniMax M2.5, z hojnymi limitami i niezawodnym dostępem.", "go.faq.q3": "Czy Go to to samo co Zen?", "go.faq.a3": - "Nie. Zen działa w modelu pay-as-you-go (płacisz za użycie), podczas gdy Go to subskrypcja za $10/miesiąc z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5, Kimi K2.5 i MiniMax M2.5.", + "Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5, Kimi K2.5 i MiniMax M2.5.", "go.faq.q4": "Ile kosztuje Go?", "go.faq.a4.p1.beforePricing": "Go kosztuje", - "go.faq.a4.p1.pricingLink": "$10/miesiąc", - "go.faq.a4.p1.afterPricing": "z hojnymi limitami.", + "go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc", + "go.faq.a4.p1.afterPricing": "potem $10/miesiąc z hojnymi limitami.", "go.faq.a4.p2.beforeAccount": "Możesz zarządzać subskrypcją na swoim", "go.faq.a4.p2.accountLink": "koncie", "go.faq.a4.p3": "Anuluj w dowolnym momencie.", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index d114e188e3..2e0eb4b519 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Недорогие модели для кодинга для всех", "go.meta.description": - "Go — это подписка за $10/месяц с щедрыми 5-часовыми лимитами запросов для GLM-5, Kimi K2.5 и MiniMax M2.5.", + "Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5, Kimi K2.5 и MiniMax M2.5.", "go.hero.title": "Недорогие модели для кодинга для всех", "go.hero.body": "Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.", @@ -264,7 +264,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Подписаться на Go", "go.cta.price": "$10/месяц", - "go.pricing.body": "Используйте с любым агентом. Пополняйте баланс при необходимости. Отменяйте в любое время.", + "go.cta.promo": "$5 первый месяц", + "go.pricing.body": "Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.", "go.graph.free": "Бесплатно", "go.graph.freePill": "Big Pickle и бесплатные модели", "go.graph.go": "Go", @@ -297,20 +298,20 @@ export const dict = { "go.testimonials.frank.quote": "Жаль, что я больше не в Nvidia.", "go.problem.title": "Какую проблему решает Go?", "go.problem.body": - "Мы сосредоточены на том, чтобы сделать OpenCode доступным как можно большему числу людей. OpenCode Go — это недорогая ($10/месяц) подписка, разработанная, чтобы сделать агентов-программистов доступными для разработчиков по всему миру. Она предоставляет щедрые лимиты и надежный доступ к самым способным моделям с открытым исходным кодом.", + "Мы стремимся сделать OpenCode доступным для как можно большего числа людей. OpenCode Go - это недорогая подписка: $5 за первый месяц, затем $10/месяц. Она предоставляет щедрые лимиты и надежный доступ к самым мощным моделям с открытым исходным кодом.", "go.problem.subtitle": " ", "go.problem.item1": "Недорогая подписка", "go.problem.item2": "Щедрые лимиты и надежный доступ", "go.problem.item3": "Создан для максимального числа программистов", "go.problem.item4": "Включает GLM-5, Kimi K2.5 и MiniMax M2.5", "go.how.title": "Как работает Go", - "go.how.body": "Go — это подписка за $10/месяц, которую можно использовать с OpenCode или любым агентом.", + "go.how.body": "Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.", "go.how.step1.title": "Создайте аккаунт", "go.how.step1.beforeLink": "следуйте", "go.how.step1.link": "инструкциям по настройке", "go.how.step2.title": "Подпишитесь на Go", - "go.how.step2.link": "$10/месяц", - "go.how.step2.afterLink": "с щедрыми лимитами", + "go.how.step2.link": "$5 за первый месяц", + "go.how.step2.afterLink": "затем $10/месяц с щедрыми лимитами", "go.how.step3.title": "Начните кодить", "go.how.step3.body": "с надежным доступом к open-source моделям", "go.privacy.title": "Ваша приватность важна для нас", @@ -327,11 +328,11 @@ export const dict = { "go.faq.a2": "Go включает GLM-5, Kimi K2.5 и MiniMax M2.5, с щедрыми лимитами и надежным доступом.", "go.faq.q3": "Go — это то же самое, что и Zen?", "go.faq.a3": - "Нет. Zen работает по системе оплаты за использование (pay-as-you-go), тогда как Go — это подписка за $10/месяц с щедрыми лимитами и надежным доступом к open-source моделям GLM-5, Kimi K2.5 и MiniMax M2.5.", + "Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5, Kimi K2.5 и MiniMax M2.5.", "go.faq.q4": "Сколько стоит Go?", "go.faq.a4.p1.beforePricing": "Go стоит", - "go.faq.a4.p1.pricingLink": "$10/месяц", - "go.faq.a4.p1.afterPricing": "с щедрыми лимитами.", + "go.faq.a4.p1.pricingLink": "$5 за первый месяц", + "go.faq.a4.p1.afterPricing": "затем $10/месяц с щедрыми лимитами.", "go.faq.a4.p2.beforeAccount": "Вы можете управлять подпиской в своем", "go.faq.a4.p2.accountLink": "аккаунте", "go.faq.a4.p3": "Отмена в любое время.", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index f74c563231..424f1577d8 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.meta.description": - "Go คือการสมัครสมาชิกราคา $10/เดือน พร้อมขีดจำกัดการร้องขอที่กว้างขวางถึง 5 ชั่วโมงสำหรับ GLM-5, Kimi K2.5 และ MiniMax M2.5", + "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5, Kimi K2.5 และ MiniMax M2.5", "go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.hero.body": "Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน", @@ -259,7 +259,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "สมัครสมาชิก Go", "go.cta.price": "$10/เดือน", - "go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ เติมเงินเครดิตหากต้องการ ยกเลิกได้ตลอดเวลา", + "go.cta.promo": "$5 เดือนแรก", + "go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $5 ในเดือนแรก จากนั้น $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา", "go.graph.free": "ฟรี", "go.graph.freePill": "Big Pickle และโมเดลฟรี", "go.graph.go": "Go", @@ -291,20 +292,20 @@ export const dict = { "go.testimonials.frank.quote": "ผมหวังว่าผมจะยังอยู่ที่ Nvidia", "go.problem.title": "Go แก้ปัญหาอะไร?", "go.problem.body": - "เรามุ่งเน้นที่จะนำประสบการณ์ OpenCode ไปสู่ผู้คนให้ได้มากที่สุด OpenCode Go เป็นการสมัครสมาชิกราคาประหยัด ($10/เดือน) ที่ออกแบบมาเพื่อนำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก โดยมอบขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ", + "เรามุ่งมั่นที่จะนำประสบการณ์ OpenCode ไปสู่ผู้คนให้ได้มากที่สุด OpenCode Go เป็นการสมัครสมาชิกราคาประหยัด: $5 สำหรับเดือนแรก จากนั้น $10/เดือน โดยมอบขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดอย่างเชื่อถือได้", "go.problem.subtitle": " ", "go.problem.item1": "ราคาการสมัครสมาชิกที่ต่ำ", "go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้", "go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้", "go.problem.item4": "รวมถึง GLM-5, Kimi K2.5 และ MiniMax M2.5", "go.how.title": "Go ทำงานอย่างไร", - "go.how.body": "Go คือการสมัครสมาชิกราคา $10/เดือน ที่คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้", + "go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้", "go.how.step1.title": "สร้างบัญชี", "go.how.step1.beforeLink": "ทำตาม", "go.how.step1.link": "คำแนะนำการตั้งค่า", "go.how.step2.title": "สมัครสมาชิก Go", - "go.how.step2.link": "$10/เดือน", - "go.how.step2.afterLink": "ด้วยขีดจำกัดที่กว้างขวาง", + "go.how.step2.link": "$5 เดือนแรก", + "go.how.step2.afterLink": "จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อ", "go.how.step3.title": "เริ่มเขียนโค้ด", "go.how.step3.body": "ด้วยการเข้าถึงโมเดลโอเพนซอร์สที่เชื่อถือได้", "go.privacy.title": "ความเป็นส่วนตัวของคุณสำคัญสำหรับเรา", @@ -321,11 +322,11 @@ export const dict = { "go.faq.a2": "Go รวมถึง GLM-5, Kimi K2.5 และ MiniMax M2.5 พร้อมขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้", "go.faq.q3": "Go เหมือนกับ Zen หรือไม่?", "go.faq.a3": - "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน (pay-as-you-go) ในขณะที่ Go เป็นการสมัครสมาชิกราคา $10/เดือน พร้อมขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5, Kimi K2.5 และ MiniMax M2.5 ได้อย่างน่าเชื่อถือ", + "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5, Kimi K2.5 และ MiniMax M2.5 อย่างเชื่อถือได้", "go.faq.q4": "Go ราคาเท่าไหร่?", "go.faq.a4.p1.beforePricing": "Go ราคา", - "go.faq.a4.p1.pricingLink": "$10/เดือน", - "go.faq.a4.p1.afterPricing": "พร้อมขีดจำกัดที่กว้างขวาง", + "go.faq.a4.p1.pricingLink": "$5 เดือนแรก", + "go.faq.a4.p1.afterPricing": "จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อ", "go.faq.a4.p2.beforeAccount": "คุณสามารถจัดการการสมัครสมาชิกของคุณได้ใน", "go.faq.a4.p2.accountLink": "บัญชีของคุณ", "go.faq.a4.p3": "ยกเลิกได้ตลอดเวลา", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index c685bf03dd..7feae9e41e 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri", "go.meta.description": - "Go, GLM-5, Kimi K2.5 ve MiniMax M2.5 için cömert 5 saatlik istek limitleri sunan aylık 10$'lık bir aboneliktir.", + "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5, Kimi K2.5 ve MiniMax M2.5 için cömert 5 saatlik istek limitleri sunar.", "go.hero.title": "Herkes için düşük maliyetli kodlama modelleri", "go.hero.body": "Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.", @@ -262,7 +262,9 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "Go'ya abone ol", "go.cta.price": "Ayda 10$", - "go.pricing.body": "Herhangi bir ajanla kullanın. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.", + "go.cta.promo": "İlk ay $5", + "go.pricing.body": + "Herhangi bir ajanla kullanın. İlk ay $5, sonrasında ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.", "go.graph.free": "Ücretsiz", "go.graph.freePill": "Big Pickle ve ücretsiz modeller", "go.graph.go": "Go", @@ -295,20 +297,21 @@ export const dict = { "go.testimonials.frank.quote": "Keşke hala Nvidia'da olsaydım.", "go.problem.title": "Go hangi sorunu çözüyor?", "go.problem.body": - "OpenCode deneyimini mümkün olduğunca çok kişiye ulaştırmaya odaklanıyoruz. OpenCode Go, ajan tabanlı kodlamayı dünya çapındaki programcılara sunmak için tasarlanmış düşük maliyetli (ayda 10$) bir aboneliktir. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sağlar.", + "OpenCode deneyimini mümkün olduğunca çok kişiye ulaştırmaya odaklandık. OpenCode Go düşük maliyetli bir aboneliktir: İlk ay $5, sonrasında ayda 10$. Cömert limitler ve en yetenekli açık kaynak modellere güvenilir erişim sağlar.", "go.problem.subtitle": " ", "go.problem.item1": "Düşük maliyetli abonelik fiyatlandırması", "go.problem.item2": "Cömert limitler ve güvenilir erişim", "go.problem.item3": "Mümkün olduğunca çok programcı için geliştirildi", "go.problem.item4": "GLM-5, Kimi K2.5 ve MiniMax M2.5 içerir", "go.how.title": "Go nasıl çalışır?", - "go.how.body": "Go, OpenCode veya herhangi bir ajanla kullanabileceğiniz aylık 10$'lık bir aboneliktir.", + "go.how.body": + "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.", "go.how.step1.title": "Bir hesap oluşturun", "go.how.step1.beforeLink": "takip edin", "go.how.step1.link": "kurulum talimatları", "go.how.step2.title": "Go'ya abone olun", - "go.how.step2.link": "Ayda 10$", - "go.how.step2.afterLink": ", cömert limitlerle", + "go.how.step2.link": "İlk ay $5", + "go.how.step2.afterLink": "sonrasında cömert limitlerle ayda 10$", "go.how.step3.title": "Kodlamaya başlayın", "go.how.step3.body": "açık kaynaklı modellere güvenilir erişimle", "go.privacy.title": "Gizliliğiniz bizim için önemlidir", @@ -325,11 +328,11 @@ export const dict = { "go.faq.a2": "Go, cömert limitler ve güvenilir erişim ile GLM-5, Kimi K2.5 ve MiniMax M2.5 modellerini içerir.", "go.faq.q3": "Go, Zen ile aynı mı?", "go.faq.a3": - "Hayır. Zen kullandıkça öde sistemidir; Go ise GLM-5, Kimi K2.5 ve MiniMax M2.5 açık kaynak modellerine cömert limitler ve güvenilir erişim sağlayan aylık 10$'lık bir aboneliktir.", + "Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5, Kimi K2.5 ve MiniMax M2.5 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.", "go.faq.q4": "Go ne kadar?", "go.faq.a4.p1.beforePricing": "Go'nun maliyeti", - "go.faq.a4.p1.pricingLink": "ayda 10$", - "go.faq.a4.p1.afterPricing": ", cömert limitlerle.", + "go.faq.a4.p1.pricingLink": "İlk ay $5", + "go.faq.a4.p1.afterPricing": "sonrasında cömert limitlerle ayda 10$.", "go.faq.a4.p2.beforeAccount": "Aboneliğinizi", "go.faq.a4.p2.accountLink": "hesabınızdan", "go.faq.a4.p3": "yönetebilirsiniz. İstediğiniz zaman iptal edin.", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index bbfc0df117..07af58c7e5 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -240,7 +240,7 @@ export const dict = { "zen.privacy.exceptionsLink": "以下例外情况除外", "go.title": "OpenCode Go | 人人可用的低成本编程模型", - "go.meta.description": "Go 是每月 $10 的订阅服务,提供对 GLM-5, Kimi K2.5, 和 MiniMax M2.5 的 5 小时内充裕请求限额。", + "go.meta.description": "Go 首月 $5,之后 $10/月,提供对 GLM-5、Kimi K2.5 和 MiniMax M2.5 的 5 小时充裕请求额度。", "go.hero.title": "人人可用的低成本编程模型", "go.hero.body": "Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。", @@ -249,7 +249,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "订阅 Go", "go.cta.price": "$10/月", - "go.pricing.body": "可配合任何代理使用。按需充值。随时取消。", + "go.cta.promo": "首月 $5", + "go.pricing.body": "可配合任何代理使用。首月 $5,之后 $10/月。如有需要可充值。随时取消。", "go.graph.free": "免费", "go.graph.freePill": "Big Pickle 和免费模型", "go.graph.go": "Go", @@ -281,20 +282,20 @@ export const dict = { "go.testimonials.frank.quote": "我希望我还在 Nvidia。", "go.problem.title": "Go 解决了什么问题?", "go.problem.body": - "我们致力于将 OpenCode 体验带给尽可能多的人。OpenCode Go 是一个低成本 ($10/月) 的订阅服务,旨在将代理编程带给全世界的程序员。它提供充裕的限额和对最强大的开源模型的可靠访问。", + "我们致力于将 OpenCode 体验带给尽可能多的人。OpenCode Go 是一款低成本订阅服务:首月 $5,之后 $10/月。它提供充裕的额度,并让您能可靠地使用最强大的开源模型。", "go.problem.subtitle": " ", "go.problem.item1": "低成本订阅定价", "go.problem.item2": "充裕的限额和可靠的访问", "go.problem.item3": "为尽可能多的程序员打造", "go.problem.item4": "包含 GLM-5, Kimi K2.5, 和 MiniMax M2.5", "go.how.title": "Go 如何工作", - "go.how.body": "Go 是每月 $10 的订阅服务,您可以配合 OpenCode 或任何代理使用。", + "go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "创建账户", "go.how.step1.beforeLink": "遵循", "go.how.step1.link": "设置说明", "go.how.step2.title": "订阅 Go", - "go.how.step2.link": "$10/月", - "go.how.step2.afterLink": "享受充裕限额", + "go.how.step2.link": "首月 $5", + "go.how.step2.afterLink": "之后 $10/月,额度充裕", "go.how.step3.title": "开始编程", "go.how.step3.body": "可靠访问开源模型", "go.privacy.title": "您的隐私对我们很重要", @@ -308,11 +309,11 @@ export const dict = { "go.faq.a2": "Go 包含 GLM-5, Kimi K2.5, 和 MiniMax M2.5,并提供充裕的限额和可靠的访问。", "go.faq.q3": "Go 和 Zen 一样吗?", "go.faq.a3": - "不一样。Zen 是即用即付,而 Go 是每月 $10 的订阅服务,提供对开源模型 GLM-5, Kimi K2.5, 和 MiniMax M2.5 的充裕限额和可靠访问。", + "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5、Kimi K2.5 和 MiniMax M2.5 等开源模型。", "go.faq.q4": "Go 多少钱?", "go.faq.a4.p1.beforePricing": "Go 费用为", - "go.faq.a4.p1.pricingLink": "$10/月", - "go.faq.a4.p1.afterPricing": "包含充裕限额。", + "go.faq.a4.p1.pricingLink": "首月 $5", + "go.faq.a4.p1.afterPricing": "之后 $10/月,额度充裕。", "go.faq.a4.p2.beforeAccount": "您可以在您的", "go.faq.a4.p2.accountLink": "账户", "go.faq.a4.p3": "中管理订阅。随时取消。", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 6a5ce0f8ad..97125d79f9 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -241,7 +241,7 @@ export const dict = { "go.title": "OpenCode Go | 低成本全民編碼模型", "go.meta.description": - "Go 是一個每月 $10 的訂閱方案,提供對 GLM-5、Kimi K2.5 與 MiniMax M2.5 的 5 小時寬裕使用限額。", + "Go 首月 $5,之後 $10/月,提供對 GLM-5、Kimi K2.5 和 MiniMax M2.5 的 5 小時充裕請求額度。", "go.hero.title": "低成本全民編碼模型", "go.hero.body": "Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。", @@ -250,7 +250,8 @@ export const dict = { "go.cta.template": "{{text}} {{price}}", "go.cta.text": "訂閱 Go", "go.cta.price": "$10/月", - "go.pricing.body": "可與任何代理一起使用。需要時可儲值額度。隨時取消。", + "go.cta.promo": "首月 $5", + "go.pricing.body": "可搭配任何代理使用。首月 $5,之後 $10/月。如有需要可儲值。隨時取消。", "go.graph.free": "免費", "go.graph.freePill": "Big Pickle 與免費模型", "go.graph.go": "Go", @@ -282,20 +283,20 @@ export const dict = { "go.testimonials.frank.quote": "我希望我還在 Nvidia。", "go.problem.title": "Go 正在解決什麼問題?", "go.problem.body": - "我們致力於將 OpenCode 體驗帶給盡可能多的人。OpenCode Go 是一個低成本(每月 $10)的訂閱方案,旨在將代理編碼帶給全世界的程式設計師。它提供寬裕的限額以及對最強大開源模型的穩定存取。", + "我們致力於將 OpenCode 體驗帶給盡可能多的人。OpenCode Go 是一款低成本訂閱服務:首月 $5,之後 $10/月。它提供充裕的額度,並讓您能可靠地使用最強大的開源模型。", "go.problem.subtitle": " ", "go.problem.item1": "低成本訂閱定價", "go.problem.item2": "寬裕的限額與穩定存取", "go.problem.item3": "專為盡可能多的程式設計師打造", "go.problem.item4": "包含 GLM-5、Kimi K2.5 與 MiniMax M2.5", "go.how.title": "Go 如何運作", - "go.how.body": "Go 是一個每月 $10 的訂閱方案,你可以將其與 OpenCode 或任何代理一起使用。", + "go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "建立帳號", "go.how.step1.beforeLink": "遵循", "go.how.step1.link": "設定說明", "go.how.step2.title": "訂閱 Go", - "go.how.step2.link": "$10/月", - "go.how.step2.afterLink": "享寬裕限額", + "go.how.step2.link": "首月 $5", + "go.how.step2.afterLink": "之後 $10/月,額度充裕", "go.how.step3.title": "開始編碼", "go.how.step3.body": "穩定存取開源模型", "go.privacy.title": "你的隱私對我們很重要", @@ -309,11 +310,11 @@ export const dict = { "go.faq.a2": "Go 包含 GLM-5、Kimi K2.5 與 MiniMax M2.5,並提供寬裕的限額與穩定存取。", "go.faq.q3": "Go 與 Zen 一樣嗎?", "go.faq.a3": - "不一樣。Zen 是按量付費,而 Go 是每月 $10 的訂閱方案,提供對開源模型 GLM-5、Kimi K2.5 與 MiniMax M2.5 的寬裕限額與穩定存取。", + "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5、Kimi K2.5 和 MiniMax M2.5 等開源模型。", "go.faq.q4": "Go 費用是多少?", "go.faq.a4.p1.beforePricing": "Go 費用為", - "go.faq.a4.p1.pricingLink": "$10/月", - "go.faq.a4.p1.afterPricing": "享寬裕限額。", + "go.faq.a4.p1.pricingLink": "首月 $5", + "go.faq.a4.p1.afterPricing": "之後 $10/月,額度充裕。", "go.faq.a4.p2.beforeAccount": "你可以在你的", "go.faq.a4.p2.accountLink": "帳戶", "go.faq.a4.p3": "中管理訂閱。隨時取消。", diff --git a/packages/console/app/src/routes/go/index.css b/packages/console/app/src/routes/go/index.css index d3239d482f..c73e6f0d09 100644 --- a/packages/console/app/src/routes/go/index.css +++ b/packages/console/app/src/routes/go/index.css @@ -368,7 +368,18 @@ body { text-decoration: none; [data-slot="cta-price"] { - opacity: 0.6; + display: inline-flex; + align-items: center; + gap: 8px; + } + + [data-slot="cta-price-old"] { + opacity: 0.45; + text-decoration: line-through; + } + + [data-slot="cta-price-new"] { + opacity: 1; } svg { diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index 8f1930bd7b..c9a1424bb7 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -320,7 +320,14 @@ export default function Home() { > {(part) => { if (part === "{{text}}") return <span>{i18n.t("go.cta.text")}</span> - if (part === "{{price}}") return <span data-slot="cta-price">{i18n.t("go.cta.price")}</span> + if (part === "{{price}}") { + return ( + <span data-slot="cta-price"> + <span data-slot="cta-price-old">{i18n.t("go.cta.price")}</span> + <span data-slot="cta-price-new">{i18n.t("go.cta.promo")}</span> + </span> + ) + } return part }} </For> From 502d6db6d0de384a2831d059b974811513937f08 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 10:46:16 -0400 Subject: [PATCH 101/200] go: first month discount --- infra/console.ts | 7 +++++++ packages/console/app/src/i18n/ar.ts | 3 ++- packages/console/app/src/i18n/br.ts | 3 ++- packages/console/app/src/i18n/da.ts | 3 ++- packages/console/app/src/i18n/de.ts | 3 ++- packages/console/app/src/i18n/en.ts | 3 ++- packages/console/app/src/i18n/es.ts | 3 ++- packages/console/app/src/i18n/fr.ts | 3 ++- packages/console/app/src/i18n/it.ts | 3 ++- packages/console/app/src/i18n/ja.ts | 3 ++- packages/console/app/src/i18n/ko.ts | 3 ++- packages/console/app/src/i18n/no.ts | 3 ++- packages/console/app/src/i18n/pl.ts | 3 ++- packages/console/app/src/i18n/ru.ts | 3 ++- packages/console/app/src/i18n/th.ts | 3 ++- packages/console/app/src/i18n/tr.ts | 3 ++- packages/console/app/src/i18n/zh.ts | 3 ++- packages/console/app/src/i18n/zht.ts | 3 ++- .../workspace/[id]/go/lite-section.module.css | 5 +++++ .../routes/workspace/[id]/go/lite-section.tsx | 16 ++++++++++++++-- packages/console/core/src/billing.ts | 1 + packages/console/core/src/lite.ts | 1 + packages/console/core/sst-env.d.ts | 1 + packages/console/function/sst-env.d.ts | 1 + packages/console/resource/sst-env.d.ts | 1 + packages/enterprise/sst-env.d.ts | 1 + packages/function/sst-env.d.ts | 1 + sst-env.d.ts | 1 + 28 files changed, 68 insertions(+), 19 deletions(-) diff --git a/infra/console.ts b/infra/console.ts index 128e069863..c7889c587f 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -103,6 +103,12 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint", const zenLiteProduct = new stripe.Product("ZenLite", { name: "OpenCode Go", }) +const zenLiteCouponFirstMonth50 = new stripe.Coupon("ZenLiteCouponFirstMonth50", { + name: "First month 50% off", + percentOff: 50, + appliesToProducts: [zenLiteProduct.id], + duration: "once", +}) const zenLitePrice = new stripe.Price("ZenLitePrice", { product: zenLiteProduct.id, currency: "usd", @@ -116,6 +122,7 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", { properties: { product: zenLiteProduct.id, price: zenLitePrice.id, + firstMonth50Coupon: zenLiteCouponFirstMonth50.id, }, }) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index ef163fa269..1588f05549 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -635,7 +635,8 @@ export const dict = { "workspace.lite.other.message": "عضو آخر في مساحة العمل هذه مشترك بالفعل في OpenCode Go. يمكن لعضو واحد فقط لكل مساحة عمل الاشتراك.", "workspace.lite.promo.description": - "OpenCode Go هو اشتراك بسعر $10 شهريًا يوفر وصولاً موثوقًا إلى نماذج البرمجة المفتوحة الشائعة مع حدود استخدام سخية.", + "يبدأ OpenCode Go بسعر {{price}}، ثم $10/شهر، ويوفر وصولا موثوقا لنماذج البرمجة المفتوحة الشهيرة مع حدود استخدام سخية.", + "workspace.lite.promo.price": "$5 للشهر الأول", "workspace.lite.promo.modelsTitle": "ما يتضمنه", "workspace.lite.promo.footer": "تم تصميم الخطة بشكل أساسي للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة للحصول على وصول عالمي مستقر. قد تتغير الأسعار وحدود الاستخدام بناءً على تعلمنا من الاستخدام المبكر والملاحظات.", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index d7d2a48d47..4d74c05a3b 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -644,7 +644,8 @@ export const dict = { "workspace.lite.other.message": "Outro membro neste workspace já assina o OpenCode Go. Apenas um membro por workspace pode assinar.", "workspace.lite.promo.description": - "O OpenCode Go é uma assinatura de $10 por mês que fornece acesso confiável a modelos abertos de codificação populares com limites de uso generosos.", + "O OpenCode Go começa em {{price}}, depois $10/mês, e oferece acesso confiável a modelos de codificação abertos populares com limites de uso generosos.", + "workspace.lite.promo.price": "$5 no primeiro mês", "workspace.lite.promo.modelsTitle": "O que está incluído", "workspace.lite.promo.footer": "O plano é projetado principalmente para usuários internacionais, com modelos hospedados nos EUA, UE e Singapura para acesso global estável. Preços e limites de uso podem mudar conforme aprendemos com o uso inicial e feedback.", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 541557f7fd..45f0449164 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -640,7 +640,8 @@ export const dict = { "workspace.lite.other.message": "Et andet medlem i dette workspace abonnerer allerede på OpenCode Go. Kun ét medlem pr. workspace kan abonnere.", "workspace.lite.promo.description": - "OpenCode Go er et abonnement til $10 om måneden, der giver pålidelig adgang til populære åbne kodningsmodeller med generøse forbrugsgrænser.", + "OpenCode Go starter ved {{price}}, derefter $10/måned, og giver pålidelig adgang til populære åbne kodningsmodeller med generøse brugsgrænser.", + "workspace.lite.promo.price": "$5 for den første måned", "workspace.lite.promo.modelsTitle": "Hvad er inkluderet", "workspace.lite.promo.footer": "Planen er primært designet til internationale brugere, med modeller hostet i USA, EU og Singapore for stabil global adgang. Priser og forbrugsgrænser kan ændre sig, efterhånden som vi lærer af tidlig brug og feedback.", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 4097b4cbd1..898e70fe35 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -643,7 +643,8 @@ export const dict = { "workspace.lite.other.message": "Ein anderes Mitglied in diesem Workspace hat OpenCode Go bereits abonniert. Nur ein Mitglied pro Workspace kann abonnieren.", "workspace.lite.promo.description": - "OpenCode Go ist ein Abonnement für $10 pro Monat, das zuverlässigen Zugriff auf beliebte offene Coding-Modelle mit großzügigen Nutzungslimits bietet.", + "OpenCode Go startet bei {{price}}, danach $10/Monat, und bietet zuverlässigen Zugang zu beliebten offenen Coding-Modellen mit großzügigen Nutzungslimits.", + "workspace.lite.promo.price": "$5 im ersten Monat", "workspace.lite.promo.modelsTitle": "Was enthalten ist", "workspace.lite.promo.footer": "Der Plan wurde hauptsächlich für internationale Nutzer entwickelt, wobei die Modelle in den USA, der EU und Singapur gehostet werden, um einen stabilen weltweiten Zugriff zu gewährleisten. Preise und Nutzungslimits können sich ändern, während wir aus der frühen Nutzung und dem Feedback lernen.", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 2fc5d359a7..fbdf5addc9 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -637,7 +637,8 @@ export const dict = { "workspace.lite.other.message": "Another member in this workspace is already subscribed to OpenCode Go. Only one member per workspace can subscribe.", "workspace.lite.promo.description": - "OpenCode Go is a $10 per month subscription that provides reliable access to popular open coding models with generous usage limits.", + "OpenCode Go starts at {{price}}, then $10/month, and provides reliable access to popular open coding models with generous usage limits.", + "workspace.lite.promo.price": "$5 for your first month", "workspace.lite.promo.modelsTitle": "What's Included", "workspace.lite.promo.footer": "The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. Pricing and usage limits may change as we learn from early usage and feedback.", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 3efd8e2d1d..3407d1fde1 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -645,7 +645,8 @@ export const dict = { "workspace.lite.other.message": "Otro miembro de este espacio de trabajo ya está suscrito a OpenCode Go. Solo un miembro por espacio de trabajo puede suscribirse.", "workspace.lite.promo.description": - "OpenCode Go es una suscripción de $10 al mes que proporciona acceso confiable a modelos de codificación abiertos populares con generosos límites de uso.", + "OpenCode Go comienza en {{price}}, luego $10/mes, y ofrece acceso confiable a modelos de codificación abiertos populares con límites de uso generosos.", + "workspace.lite.promo.price": "$5 el primer mes", "workspace.lite.promo.modelsTitle": "Qué incluye", "workspace.lite.promo.footer": "El plan está diseñado principalmente para usuarios internacionales, con modelos alojados en EE. UU., la UE y Singapur para un acceso global estable. Los precios y los límites de uso pueden cambiar a medida que aprendemos del uso inicial y los comentarios.", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 06f6dfb48f..fcfc8c6eb9 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -650,7 +650,8 @@ export const dict = { "workspace.lite.other.message": "Un autre membre de cet espace de travail est déjà abonné à OpenCode Go. Un seul membre par espace de travail peut s'abonner.", "workspace.lite.promo.description": - "OpenCode Go est un abonnement à 10 $ par mois qui offre un accès fiable aux modèles de codage ouverts populaires avec des limites d'utilisation généreuses.", + "OpenCode Go commence à {{price}}, puis 10 $/mois, et offre un accès fiable aux modèles de code ouverts populaires avec des limites d'utilisation généreuses.", + "workspace.lite.promo.price": "$5 le premier mois", "workspace.lite.promo.modelsTitle": "Ce qui est inclus", "workspace.lite.promo.footer": "Le plan est conçu principalement pour les utilisateurs internationaux, avec des modèles hébergés aux États-Unis, dans l'UE et à Singapour pour un accès mondial stable. Les tarifs et les limites d'utilisation peuvent changer à mesure que nous apprenons des premières utilisations et des commentaires.", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index f68092cc1c..63d0f72d70 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -643,7 +643,8 @@ export const dict = { "workspace.lite.other.message": "Un altro membro in questo workspace è già abbonato a OpenCode Go. Solo un membro per workspace può abbonarsi.", "workspace.lite.promo.description": - "OpenCode Go è un abbonamento a $10 al mese che fornisce un accesso affidabile a popolari modelli di coding aperti con generosi limiti di utilizzo.", + "OpenCode Go parte da {{price}}, poi $10/mese, e offre un accesso affidabile a popolari modelli di coding aperti con generosi limiti di utilizzo.", + "workspace.lite.promo.price": "$5 il primo mese", "workspace.lite.promo.modelsTitle": "Cosa è incluso", "workspace.lite.promo.footer": "Il piano è progettato principalmente per gli utenti internazionali, con modelli ospitati in US, EU e Singapore per un accesso globale stabile. I prezzi e i limiti di utilizzo potrebbero cambiare man mano che impariamo dall'utilizzo iniziale e dal feedback.", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 34e5ebf8bd..e74bc666b6 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -643,7 +643,8 @@ export const dict = { "workspace.lite.other.message": "このワークスペースの別のメンバーが既に OpenCode Go を購読しています。ワークスペースにつき1人のメンバーのみが購読できます。", "workspace.lite.promo.description": - "OpenCode Goは月額$10のサブスクリプションプランで、人気のオープンコーディングモデルへの安定したアクセスを十分な利用枠で提供します。", + "OpenCode Goは{{price}}で始まり、その後は$10/月で、人気の高いオープンコーディングモデルへの安定したアクセスと余裕のある利用枠を提供します。", + "workspace.lite.promo.price": "初月$5", "workspace.lite.promo.modelsTitle": "含まれるもの", "workspace.lite.promo.footer": "このプランは主にグローバルユーザー向けに設計されており、米国、EU、シンガポールでホストされたモデルにより安定したグローバルアクセスを提供します。料金と利用制限は、初期の利用状況やフィードバックに基づいて変更される可能性があります。", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index 3ebcbfc9f5..ca7b4be93b 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -635,7 +635,8 @@ export const dict = { "workspace.lite.other.message": "이 워크스페이스의 다른 멤버가 이미 OpenCode Go를 구독 중입니다. 워크스페이스당 한 명의 멤버만 구독할 수 있습니다.", "workspace.lite.promo.description": - "OpenCode Go는 넉넉한 사용 한도와 함께 인기 있는 오픈 코딩 모델에 대한 안정적인 액세스를 제공하는 월 $10의 구독입니다.", + "OpenCode Go는 {{price}}부터 시작하며, 이후 $10/월로 넉넉한 사용량 한도와 함께 인기 있는 오픈 코딩 모델에 대한 안정적인 액세스를 제공합니다.", + "workspace.lite.promo.price": "첫 달 $5", "workspace.lite.promo.modelsTitle": "포함 내역", "workspace.lite.promo.footer": "이 플랜은 주로 글로벌 사용자를 위해 설계되었으며, 안정적인 글로벌 액세스를 위해 미국, EU 및 싱가포르에 모델이 호스팅되어 있습니다. 가격 및 사용 한도는 초기 사용을 통해 학습하고 피드백을 수집함에 따라 변경될 수 있습니다.", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index f3ee28c061..269db2438f 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -641,7 +641,8 @@ export const dict = { "workspace.lite.other.message": "Et annet medlem i dette arbeidsområdet abonnerer allerede på OpenCode Go. Kun ett medlem per arbeidsområde kan abonnere.", "workspace.lite.promo.description": - "OpenCode Go er et abonnement til $10 per måned som gir pålitelig tilgang til populære åpne kodemodeller med rause bruksgrenser.", + "OpenCode Go starter på {{price}}, deretter $10/måned, og gir pålitelig tilgang til populære åpne kodingsmodeller med sjenerøse bruksgrenser.", + "workspace.lite.promo.price": "$5 for den første måneden", "workspace.lite.promo.modelsTitle": "Hva som er inkludert", "workspace.lite.promo.footer": "Planen er primært designet for internasjonale brukere, med modeller driftet i USA, EU og Singapore for stabil global tilgang. Priser og bruksgrenser kan endres etter hvert som vi lærer fra tidlig bruk og tilbakemeldinger.", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 8f1b00f2df..f8d042af4c 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -642,7 +642,8 @@ export const dict = { "workspace.lite.other.message": "Inny członek tego obszaru roboczego już subskrybuje OpenCode Go. Tylko jeden członek na obszar roboczy może subskrybować.", "workspace.lite.promo.description": - "OpenCode Go to subskrypcja za $10 miesięcznie, która zapewnia niezawodny dostęp do popularnych otwartych modeli do kodowania z hojnymi limitami użycia.", + "OpenCode Go zaczyna się od {{price}}, potem $10/miesiąc, i zapewnia niezawodny dostęp do popularnych otwartych modeli kodowania z hojnymi limitami użycia.", + "workspace.lite.promo.price": "$5 za pierwszy miesiąc", "workspace.lite.promo.modelsTitle": "Co zawiera", "workspace.lite.promo.footer": "Plan został zaprojektowany głównie dla użytkowników międzynarodowych, z modelami hostowanymi w USA, UE i Singapurze, aby zapewnić stabilny globalny dostęp. Ceny i limity użycia mogą ulec zmianie w miarę analizy wczesnego użycia i zbierania opinii.", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 2e0eb4b519..a429f37ecb 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -648,7 +648,8 @@ export const dict = { "workspace.lite.other.message": "Другой участник в этом рабочем пространстве уже подписан на OpenCode Go. Только один участник в рабочем пространстве может оформить подписку.", "workspace.lite.promo.description": - "OpenCode Go — это подписка за $10 в месяц, которая предоставляет надежный доступ к популярным открытым моделям для кодинга с щедрыми лимитами использования.", + "OpenCode Go начинается с {{price}}, затем $10/месяц и предоставляет надежный доступ к популярным открытым моделям кодирования с щедрыми лимитами использования.", + "workspace.lite.promo.price": "$5 за первый месяц", "workspace.lite.promo.modelsTitle": "Что включено", "workspace.lite.promo.footer": "План предназначен в первую очередь для международных пользователей. Модели размещены в США, ЕС и Сингапуре для стабильного глобального доступа. Цены и лимиты использования могут меняться по мере того, как мы изучаем раннее использование и собираем отзывы.", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 424f1577d8..bb1e3e344c 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -639,7 +639,8 @@ export const dict = { "workspace.lite.other.message": "สมาชิกคนอื่นใน Workspace นี้ได้สมัคร OpenCode Go แล้ว สามารถสมัครได้เพียงหนึ่งคนต่อหนึ่ง Workspace เท่านั้น", "workspace.lite.promo.description": - "OpenCode Go เป็นการสมัครสมาชิกราคา 10 ดอลลาร์ต่อเดือน ที่ให้การเข้าถึงโมเดลโอเพนโค้ดดิงยอดนิยมได้อย่างเสถียร ด้วยขีดจำกัดการใช้งานที่ครอบคลุม", + "OpenCode Go เริ่มต้นที่ {{price}} จากนั้น $10/เดือน และมอบการเข้าถึงโมเดลการเขียนโค้ดแบบเปิดยอดนิยมอย่างเสถียรพร้อมขีดจำกัดการใช้งานที่ให้มาอย่างเหลือเฟือ", + "workspace.lite.promo.price": "$5 สำหรับเดือนแรก", "workspace.lite.promo.modelsTitle": "สิ่งที่รวมอยู่ด้วย", "workspace.lite.promo.footer": "แผนนี้ออกแบบมาสำหรับผู้ใช้งานต่างประเทศเป็นหลัก โดยมีโมเดลโฮสต์อยู่ในสหรัฐอเมริกา สหภาพยุโรป และสิงคโปร์ เพื่อการเข้าถึงที่เสถียรทั่วโลก ราคาและขีดจำกัดการใช้งานอาจมีการเปลี่ยนแปลงตามที่เราได้เรียนรู้จากการใช้งานในช่วงแรกและข้อเสนอแนะ", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 7feae9e41e..5f38d4e5cf 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -646,7 +646,8 @@ export const dict = { "workspace.lite.other.message": "Bu çalışma alanındaki başka bir üye zaten OpenCode Go abonesi. Çalışma alanı başına yalnızca bir üye abone olabilir.", "workspace.lite.promo.description": - "OpenCode Go, cömert kullanım limitleriyle popüler açık kodlama modellerine güvenilir erişim sağlayan aylık 10$'lık bir aboneliktir.", + "OpenCode Go {{price}} fiyatından başlar, sonrasında ayda 10$ olur ve cömert kullanım limitleriyle popüler açık kodlama modellerine güvenilir erişim sağlar.", + "workspace.lite.promo.price": "İlk ay $5", "workspace.lite.promo.modelsTitle": "Neler Dahil", "workspace.lite.promo.footer": "Plan öncelikle uluslararası kullanıcılar için tasarlanmıştır; modeller istikrarlı küresel erişim için ABD, AB ve Singapur'da barındırılmaktadır. Erken kullanımdan öğrendikçe ve geri bildirim topladıkça fiyatlandırma ve kullanım limitleri değişebilir.", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 07af58c7e5..a8d695ffd7 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -617,7 +617,8 @@ export const dict = { "workspace.lite.black.message": "您当前已订阅 OpenCode Black 或在候补名单中。如需切换到 Go,请先取消订阅。", "workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Go。每个工作区只有一名成员可以订阅。", "workspace.lite.promo.description": - "OpenCode Go 是一个每月 $10 的订阅计划,提供对主流开源编码模型的稳定访问,并配备充足的使用额度。", + "OpenCode Go 起价为 {{price}},之后 $10/月,并提供对流行开放编码模型的可靠访问,同时享有充裕的使用限额。", + "workspace.lite.promo.price": "首月 $5", "workspace.lite.promo.modelsTitle": "包含模型", "workspace.lite.promo.footer": "该计划主要面向国际用户设计,模型部署在美国、欧盟和新加坡,以确保全球范围内的稳定访问体验。定价和使用额度可能会根据早期用户的使用情况和反馈持续调整与优化。", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 97125d79f9..964eb70893 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -618,7 +618,8 @@ export const dict = { "workspace.lite.black.message": "您目前已訂閱 OpenCode Black 或在候補名單中。若要切換至 Go,請先取消訂閱。", "workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Go。每個工作區只能有一位成員訂閱。", "workspace.lite.promo.description": - "OpenCode Go 是一個每月 $10 的訂閱方案,提供對主流開放原始碼編碼模型的穩定存取,並配備充足的使用額度。", + "OpenCode Go 起價為 {{price}},之後 $10/月,並提供對熱門開放編碼模型的可靠存取,同時享有充裕的使用額度。", + "workspace.lite.promo.price": "首月 $5", "workspace.lite.promo.modelsTitle": "包含模型", "workspace.lite.promo.footer": "該計畫主要面向國際用戶設計,模型部署在美國、歐盟和新加坡,以確保全球範圍內的穩定存取體驗。定價和使用額度可能會根據早期用戶的使用情況和回饋持續調整與優化。", diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css index 76d9bcfb09..a760753d04 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css @@ -167,6 +167,11 @@ color: var(--color-text-secondary); line-height: 1.5; margin-top: var(--space-2); + + strong { + color: var(--color-text); + font-weight: 600; + } } [data-slot="promo-models-title"] { diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index 282411e8c3..e450cf9448 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -1,6 +1,6 @@ import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router" import { createStore } from "solid-js/store" -import { createMemo, Show } from "solid-js" +import { createMemo, For, Show } from "solid-js" import { Billing } from "@opencode-ai/console-core/billing.js" import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js" import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js" @@ -271,7 +271,19 @@ export function LiteSection() { <div data-slot="section-title"> <h2>{i18n.t("workspace.lite.title")}</h2> </div> - <p data-slot="promo-description">{i18n.t("workspace.lite.promo.description")}</p> + <p data-slot="promo-description"> + <For + each={i18n + .t("workspace.lite.promo.description") + .split(/(\{\{price\}\})/g) + .filter(Boolean)} + > + {(part) => { + if (part === "{{price}}") return <strong>{i18n.t("workspace.lite.promo.price")}</strong> + return part + }} + </For> + </p> <h3 data-slot="promo-models-title">{i18n.t("workspace.lite.promo.modelsTitle")}</h3> <ul data-slot="promo-models"> <li>Kimi K2.5</li> diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index a1078052c3..77657fc63c 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -258,6 +258,7 @@ export namespace Billing { mode: "subscription", billing_address_collection: "required", line_items: [{ price: LiteData.priceID(), quantity: 1 }], + discounts: [{ coupon: LiteData.firstMonth50Coupon() }], ...(billing.customerID ? { customer: billing.customerID, diff --git a/packages/console/core/src/lite.ts b/packages/console/core/src/lite.ts index c6f7d5a3e4..8c5b63d0c7 100644 --- a/packages/console/core/src/lite.ts +++ b/packages/console/core/src/lite.ts @@ -10,5 +10,6 @@ export namespace LiteData { export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product) export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price) + export const firstMonth50Coupon = fn(z.void(), () => Resource.ZEN_LITE_PRICE.firstMonth50Coupon) export const planName = fn(z.void(), () => "lite") } diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 23ae6e44bf..d00d0c32ff 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -131,6 +131,7 @@ declare module "sst" { "value": string } "ZEN_LITE_PRICE": { + "firstMonth50Coupon": string "price": string "product": string "type": "sst.sst.Linkable" diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index 23ae6e44bf..d00d0c32ff 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -131,6 +131,7 @@ declare module "sst" { "value": string } "ZEN_LITE_PRICE": { + "firstMonth50Coupon": string "price": string "product": string "type": "sst.sst.Linkable" diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 23ae6e44bf..d00d0c32ff 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -131,6 +131,7 @@ declare module "sst" { "value": string } "ZEN_LITE_PRICE": { + "firstMonth50Coupon": string "price": string "product": string "type": "sst.sst.Linkable" diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts index 23ae6e44bf..d00d0c32ff 100644 --- a/packages/enterprise/sst-env.d.ts +++ b/packages/enterprise/sst-env.d.ts @@ -131,6 +131,7 @@ declare module "sst" { "value": string } "ZEN_LITE_PRICE": { + "firstMonth50Coupon": string "price": string "product": string "type": "sst.sst.Linkable" diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 23ae6e44bf..d00d0c32ff 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -131,6 +131,7 @@ declare module "sst" { "value": string } "ZEN_LITE_PRICE": { + "firstMonth50Coupon": string "price": string "product": string "type": "sst.sst.Linkable" diff --git a/sst-env.d.ts b/sst-env.d.ts index c8622a5a9a..48478d4e57 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -157,6 +157,7 @@ declare module "sst" { "value": string } "ZEN_LITE_PRICE": { + "firstMonth50Coupon": string "price": string "product": string "type": "sst.sst.Linkable" From bbc7052c7a34dc0e71418d04a4425d78bfeb58fc Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 17:07:39 -0400 Subject: [PATCH 102/200] go: dashboard design --- packages/console/app/src/component/icon.tsx | 82 +++++-------------- packages/console/app/src/i18n/ar.ts | 5 +- packages/console/app/src/i18n/br.ts | 8 +- packages/console/app/src/i18n/da.ts | 8 +- packages/console/app/src/i18n/de.ts | 8 +- packages/console/app/src/i18n/en.ts | 2 +- packages/console/app/src/i18n/es.ts | 5 +- packages/console/app/src/i18n/fr.ts | 8 +- packages/console/app/src/i18n/it.ts | 5 +- packages/console/app/src/i18n/ja.ts | 5 +- packages/console/app/src/i18n/ko.ts | 5 +- packages/console/app/src/i18n/no.ts | 8 +- packages/console/app/src/i18n/pl.ts | 8 +- packages/console/app/src/i18n/ru.ts | 8 +- packages/console/app/src/i18n/th.ts | 2 +- packages/console/app/src/i18n/tr.ts | 2 +- packages/console/app/src/i18n/zh.ts | 2 +- packages/console/app/src/i18n/zht.ts | 5 +- .../src/routes/workspace/[id]/go/index.tsx | 19 +++++ .../routes/workspace/[id]/go/lite-section.tsx | 10 --- .../app/src/routes/workspace/[id]/index.tsx | 6 +- 21 files changed, 97 insertions(+), 114 deletions(-) diff --git a/packages/console/app/src/component/icon.tsx b/packages/console/app/src/component/icon.tsx index 8d3c71656e..e39da4a0ef 100644 --- a/packages/console/app/src/component/icon.tsx +++ b/packages/console/app/src/component/icon.tsx @@ -1,69 +1,25 @@ import { JSX } from "solid-js" -export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) { +export function IconZen(props: JSX.SvgSVGAttributes<SVGSVGElement>) { return ( - <svg width="64" height="32" viewBox="0 0 64 32" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path d="M0 9.14333V4.5719H4.57143V9.14333H0Z" fill="currentColor" /> - <path d="M4.57178 9.14333V4.5719H9.14321V9.14333H4.57178Z" fill="currentColor" /> - <path d="M9.1438 9.14333V4.5719H13.7152V9.14333H9.1438Z" fill="currentColor" /> - <path d="M13.7124 9.14333V4.5719H18.2838V9.14333H13.7124Z" fill="currentColor" /> - <path d="M13.7124 13.7136V9.14221H18.2838V13.7136H13.7124Z" fill="currentColor" /> - <path d="M0 18.2857V13.7142H4.57143V18.2857H0Z" fill="currentColor" fill-opacity="0.2" /> - <rect width="4.57143" height="4.57143" transform="translate(4.57178 13.7141)" fill="currentColor" /> - <path d="M4.57178 18.2855V13.7141H9.14321V18.2855H4.57178Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M9.1438 18.2855V13.7141H13.7152V18.2855H9.1438Z" fill="currentColor" /> - <path d="M13.7156 18.2855V13.7141H18.287V18.2855H13.7156Z" fill="currentColor" fill-opacity="0.2" /> - <rect width="4.57143" height="4.57143" transform="translate(0 18.2859)" fill="currentColor" /> - <path d="M0 22.8572V18.2858H4.57143V22.8572H0Z" fill="currentColor" fill-opacity="0.2" /> - <rect - width="4.57143" - height="4.57143" - transform="translate(4.57178 18.2859)" - fill="currentColor" - fill-opacity="0.2" - /> - <path d="M4.57178 22.8573V18.2859H9.14321V22.8573H4.57178Z" fill="currentColor" /> - <path d="M9.1438 22.8573V18.2859H13.7152V22.8573H9.1438Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M13.7156 22.8573V18.2859H18.287V22.8573H13.7156Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M0 27.4292V22.8578H4.57143V27.4292H0Z" fill="currentColor" /> - <path d="M4.57178 27.4292V22.8578H9.14321V27.4292H4.57178Z" fill="currentColor" /> - <path d="M9.1438 27.4276V22.8562H13.7152V27.4276H9.1438Z" fill="currentColor" /> - <path d="M13.7124 27.4292V22.8578H18.2838V27.4292H13.7124Z" fill="currentColor" /> - <path d="M22.8572 9.14333V4.5719H27.4286V9.14333H22.8572Z" fill="currentColor" /> - <path d="M27.426 9.14333V4.5719H31.9975V9.14333H27.426Z" fill="currentColor" /> - <path d="M32.001 9.14333V4.5719H36.5724V9.14333H32.001Z" fill="currentColor" /> - <path d="M36.5698 9.14333V4.5719H41.1413V9.14333H36.5698Z" fill="currentColor" /> - <path d="M22.8572 13.7152V9.1438H27.4286V13.7152H22.8572Z" fill="currentColor" /> - <path d="M36.5698 13.7152V9.1438H41.1413V13.7152H36.5698Z" fill="currentColor" /> - <path d="M22.8572 18.2855V13.7141H27.4286V18.2855H22.8572Z" fill="currentColor" /> - <path d="M27.4292 18.2855V13.7141H32.0006V18.2855H27.4292Z" fill="currentColor" /> - <path d="M32.001 18.2855V13.7141H36.5724V18.2855H32.001Z" fill="currentColor" /> - <path d="M36.5698 18.2855V13.7141H41.1413V18.2855H36.5698Z" fill="currentColor" /> - <path d="M22.8572 22.8573V18.2859H27.4286V22.8573H22.8572Z" fill="currentColor" /> - <path d="M27.4292 22.8573V18.2859H32.0006V22.8573H27.4292Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M32.001 22.8573V18.2859H36.5724V22.8573H32.001Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M36.5698 22.8573V18.2859H41.1413V22.8573H36.5698Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M22.8572 27.4292V22.8578H27.4286V27.4292H22.8572Z" fill="currentColor" /> - <path d="M27.4292 27.4276V22.8562H32.0006V27.4276H27.4292Z" fill="currentColor" /> - <path d="M32.001 27.4276V22.8562H36.5724V27.4276H32.001Z" fill="currentColor" /> - <path d="M36.5698 27.4292V22.8578H41.1413V27.4292H36.5698Z" fill="currentColor" /> - <path d="M45.7144 9.14333V4.5719H50.2858V9.14333H45.7144Z" fill="currentColor" /> - <path d="M50.2861 9.14333V4.5719H54.8576V9.14333H50.2861Z" fill="currentColor" /> - <path d="M54.855 9.14333V4.5719H59.4264V9.14333H54.855Z" fill="currentColor" /> - <path d="M45.7144 13.7136V9.14221H50.2858V13.7136H45.7144Z" fill="currentColor" /> - <path d="M59.4299 13.7152V9.1438H64.0014V13.7152H59.4299Z" fill="currentColor" /> - <path d="M45.7144 18.2855V13.7141H50.2858V18.2855H45.7144Z" fill="currentColor" /> - <path d="M50.2861 18.2857V13.7142H54.8576V18.2857H50.2861Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M54.8579 18.2855V13.7141H59.4293V18.2855H54.8579Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M59.4299 18.2855V13.7141H64.0014V18.2855H59.4299Z" fill="currentColor" /> - <path d="M45.7144 22.8573V18.2859H50.2858V22.8573H45.7144Z" fill="currentColor" /> - <path d="M50.2861 22.8572V18.2858H54.8576V22.8572H50.2861Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M54.8579 22.8573V18.2859H59.4293V22.8573H54.8579Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M59.4299 22.8573V18.2859H64.0014V22.8573H59.4299Z" fill="currentColor" /> - <path d="M45.7144 27.4292V22.8578H50.2858V27.4292H45.7144Z" fill="currentColor" /> - <path d="M50.2861 27.4286V22.8572H54.8576V27.4286H50.2861Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M54.8579 27.4285V22.8571H59.4293V27.4285H54.8579Z" fill="currentColor" fill-opacity="0.2" /> - <path d="M59.4299 27.4292V22.8578H64.0014V27.4292H59.4299Z" fill="currentColor" /> + <svg width="84" height="30" viewBox="0 0 84 30" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M24 24H6V18H18V12H24V24ZM6 18H0V12H6V18Z" fill="currentColor" fill-opacity="0.2" /> + <path d="M6 24H24V30H0V18H6V24ZM18 18H6V12H18V18ZM24 12H18V6H0V0H24V12Z" fill="currentColor" /> + <path d="M54 18V24H36V18H54Z" fill="currentColor" fill-opacity="0.2" /> + <path d="M54 18H36V24H54V30H30V0H54V18ZM36 12H48V6H36V12Z" fill="currentColor" /> + <path d="M78 30H66V12H78V30Z" fill="currentColor" fill-opacity="0.2" /> + <path d="M78 6H66V30H60V0H78V6ZM84 30H78V6H84V30Z" fill="currentColor" /> + </svg> + ) +} + +export function IconGo(props: JSX.SvgSVGAttributes<SVGSVGElement>) { + return ( + <svg width="54" height="30" viewBox="0 0 54 30" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M24 30H0V0H24V6H6V24H18V18H12V12H24V30Z" fill="currentColor" /> + <path d="M12 18H18V24H6V12H12V18Z" fill="currentColor" fill-opacity="0.2" /> + <path d="M48 12V24H36V12H48Z" fill="currentColor" fill-opacity="0.2" /> + <path d="M54 30H30V0H54V30ZM36 24H48V6H36V24Z" fill="currentColor" /> </svg> ) } diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 1588f05549..74a08dfa25 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -259,7 +259,8 @@ export const dict = { "go.cta.text": "اشترك في Go", "go.cta.price": "$10/شهر", "go.cta.promo": "$5 للشهر الأول", - "go.pricing.body": "استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.", + "go.pricing.body": + "استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.", "go.graph.free": "مجاني", "go.graph.freePill": "Big Pickle ونماذج مجانية", "go.graph.go": "Go", @@ -420,6 +421,7 @@ export const dict = { "workspace.nav.settings": "الإعدادات", "workspace.home.banner.beforeLink": "نماذج محسنة وموثوقة لوكلاء البرمجة.", + "workspace.lite.banner.beforeLink": "نماذج برمجة منخفضة التكلفة للجميع.", "workspace.home.billing.loading": "جارٍ التحميل...", "workspace.home.billing.enable": "تمكين الفوترة", "workspace.home.billing.currentBalance": "الرصيد الحالي", @@ -620,7 +622,6 @@ export const dict = { "workspace.lite.time.minute": "دقيقة", "workspace.lite.time.minutes": "دقائق", "workspace.lite.time.fewSeconds": "بضع ثوان", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "أنت مشترك في OpenCode Go.", "workspace.lite.subscription.manage": "إدارة الاشتراك", "workspace.lite.subscription.rollingUsage": "الاستخدام المتجدد", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 4d74c05a3b..b0ef0983d6 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -263,7 +263,8 @@ export const dict = { "go.cta.text": "Assinar o Go", "go.cta.price": "$10/mês", "go.cta.promo": "$5 no primeiro mês", - "go.pricing.body": "Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.", + "go.pricing.body": + "Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.", "go.graph.free": "Grátis", "go.graph.freePill": "Big Pickle e modelos gratuitos", "go.graph.go": "Go", @@ -303,7 +304,8 @@ export const dict = { "go.problem.item3": "Feito para o maior número possível de programadores", "go.problem.item4": "Inclui GLM-5, Kimi K2.5 e MiniMax M2.5", "go.how.title": "Como o Go funciona", - "go.how.body": "O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.", + "go.how.body": + "O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.", "go.how.step1.title": "Crie uma conta", "go.how.step1.beforeLink": "siga as", "go.how.step1.link": "instruções de configuração", @@ -427,6 +429,7 @@ export const dict = { "workspace.nav.settings": "Configurações", "workspace.home.banner.beforeLink": "Modelos otimizados e confiáveis para agentes de codificação.", + "workspace.lite.banner.beforeLink": "Modelos de codificação de baixo custo para todos.", "workspace.home.billing.loading": "Carregando...", "workspace.home.billing.enable": "Ativar faturamento", "workspace.home.billing.currentBalance": "Saldo atual", @@ -629,7 +632,6 @@ export const dict = { "workspace.lite.time.minute": "minuto", "workspace.lite.time.minutes": "minutos", "workspace.lite.time.fewSeconds": "alguns segundos", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Você assina o OpenCode Go.", "workspace.lite.subscription.manage": "Gerenciar Assinatura", "workspace.lite.subscription.rollingUsage": "Uso Contínuo", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 45f0449164..45784969ff 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -261,7 +261,8 @@ export const dict = { "go.cta.text": "Abonner på Go", "go.cta.price": "$10/måned", "go.cta.promo": "$5 første måned", - "go.pricing.body": "Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.", + "go.pricing.body": + "Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", @@ -300,7 +301,8 @@ export const dict = { "go.problem.item3": "Bygget til så mange programmører som muligt", "go.problem.item4": "Inkluderer GLM-5, Kimi K2.5 og MiniMax M2.5", "go.how.title": "Hvordan Go virker", - "go.how.body": "Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.", + "go.how.body": + "Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.", "go.how.step1.title": "Opret en konto", "go.how.step1.beforeLink": "følg", "go.how.step1.link": "opsætningsinstruktionerne", @@ -423,6 +425,7 @@ export const dict = { "workspace.nav.settings": "Indstillinger", "workspace.home.banner.beforeLink": "Pålidelige optimerede modeller til kodningsagenter.", + "workspace.lite.banner.beforeLink": "Lavpris kodemodeller for alle.", "workspace.home.billing.loading": "Indlæser...", "workspace.home.billing.enable": "Aktiver fakturering", "workspace.home.billing.currentBalance": "Nuværende saldo", @@ -625,7 +628,6 @@ export const dict = { "workspace.lite.time.minute": "minut", "workspace.lite.time.minutes": "minutter", "workspace.lite.time.fewSeconds": "et par sekunder", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Du abonnerer på OpenCode Go.", "workspace.lite.subscription.manage": "Administrer abonnement", "workspace.lite.subscription.rollingUsage": "Løbende forbrug", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 898e70fe35..d14ec8de5b 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -263,7 +263,8 @@ export const dict = { "go.cta.text": "Go abonnieren", "go.cta.price": "$10/Monat", "go.cta.promo": "$5 im ersten Monat", - "go.pricing.body": "Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.", + "go.pricing.body": + "Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.", "go.graph.free": "Kostenlos", "go.graph.freePill": "Big Pickle und kostenlose Modelle", "go.graph.go": "Go", @@ -302,7 +303,8 @@ export const dict = { "go.problem.item3": "Für so viele Programmierer wie möglich gebaut", "go.problem.item4": "Beinhaltet GLM-5, Kimi K2.5 und MiniMax M2.5", "go.how.title": "Wie Go funktioniert", - "go.how.body": "Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.", + "go.how.body": + "Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.", "go.how.step1.title": "Konto erstellen", "go.how.step1.beforeLink": "folge den", "go.how.step1.link": "Einrichtungsanweisungen", @@ -426,6 +428,7 @@ export const dict = { "workspace.nav.settings": "Einstellungen", "workspace.home.banner.beforeLink": "Zuverlässige, optimierte Modelle für Coding-Agents.", + "workspace.lite.banner.beforeLink": "Kostengünstige Coding-Modelle für alle.", "workspace.home.billing.loading": "Laden...", "workspace.home.billing.enable": "Abrechnung aktivieren", "workspace.home.billing.currentBalance": "Aktuelles Guthaben", @@ -628,7 +631,6 @@ export const dict = { "workspace.lite.time.minute": "Minute", "workspace.lite.time.minutes": "Minuten", "workspace.lite.time.fewSeconds": "einige Sekunden", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Du hast OpenCode Go abonniert.", "workspace.lite.subscription.manage": "Abo verwalten", "workspace.lite.subscription.rollingUsage": "Fortlaufende Nutzung", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index fbdf5addc9..1e522d6e01 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -420,6 +420,7 @@ export const dict = { "workspace.nav.settings": "Settings", "workspace.home.banner.beforeLink": "Reliable optimized models for coding agents.", + "workspace.lite.banner.beforeLink": "Low cost coding models for everyone.", "workspace.home.billing.loading": "Loading...", "workspace.home.billing.enable": "Enable billing", "workspace.home.billing.currentBalance": "Current balance", @@ -622,7 +623,6 @@ export const dict = { "workspace.lite.time.minute": "minute", "workspace.lite.time.minutes": "minutes", "workspace.lite.time.fewSeconds": "a few seconds", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "You are subscribed to OpenCode Go.", "workspace.lite.subscription.manage": "Manage Subscription", "workspace.lite.subscription.rollingUsage": "Rolling Usage", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 3407d1fde1..7992e98a33 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -264,7 +264,8 @@ export const dict = { "go.cta.text": "Suscribirse a Go", "go.cta.price": "10 $/mes", "go.cta.promo": "$5 el primer mes", - "go.pricing.body": "Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.", + "go.pricing.body": + "Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle y modelos gratuitos", "go.graph.go": "Go", @@ -428,6 +429,7 @@ export const dict = { "workspace.nav.settings": "Configuración", "workspace.home.banner.beforeLink": "Modelos optimizados y confiables para agentes de codificación.", + "workspace.lite.banner.beforeLink": "Modelos de codificación de bajo costo para todos.", "workspace.home.billing.loading": "Cargando...", "workspace.home.billing.enable": "Habilitar facturación", "workspace.home.billing.currentBalance": "Saldo actual", @@ -630,7 +632,6 @@ export const dict = { "workspace.lite.time.minute": "minuto", "workspace.lite.time.minutes": "minutos", "workspace.lite.time.fewSeconds": "unos pocos segundos", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Estás suscrito a OpenCode Go.", "workspace.lite.subscription.manage": "Gestionar Suscripción", "workspace.lite.subscription.rollingUsage": "Uso Continuo", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index fcfc8c6eb9..e5c2b622fc 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -265,7 +265,8 @@ export const dict = { "go.cta.text": "S'abonner à Go", "go.cta.price": "10 $/mois", "go.cta.promo": "$5 le premier mois", - "go.pricing.body": "Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.", + "go.pricing.body": + "Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.", "go.graph.free": "Gratuit", "go.graph.freePill": "Big Pickle et modèles gratuits", "go.graph.go": "Go", @@ -304,7 +305,8 @@ export const dict = { "go.problem.item3": "Conçu pour autant de programmeurs que possible", "go.problem.item4": "Inclut GLM-5, Kimi K2.5 et MiniMax M2.5", "go.how.title": "Comment fonctionne Go", - "go.how.body": "Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.", + "go.how.body": + "Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.", "go.how.step1.title": "Créez un compte", "go.how.step1.beforeLink": "suivez les", "go.how.step1.link": "instructions de configuration", @@ -428,6 +430,7 @@ export const dict = { "workspace.nav.settings": "Paramètres", "workspace.home.banner.beforeLink": "Modèles optimisés fiables pour les agents de code.", + "workspace.lite.banner.beforeLink": "Modèles de code à faible coût pour tous.", "workspace.home.billing.loading": "Chargement...", "workspace.home.billing.enable": "Activer la facturation", "workspace.home.billing.currentBalance": "Solde actuel", @@ -634,7 +637,6 @@ export const dict = { "workspace.lite.time.minute": "minute", "workspace.lite.time.minutes": "minutes", "workspace.lite.time.fewSeconds": "quelques secondes", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Vous êtes abonné à OpenCode Go.", "workspace.lite.subscription.manage": "Gérer l'abonnement", "workspace.lite.subscription.rollingUsage": "Utilisation glissante", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 63d0f72d70..baede025d4 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -261,7 +261,8 @@ export const dict = { "go.cta.text": "Abbonati a Go", "go.cta.price": "$10/mese", "go.cta.promo": "$5 il primo mese", - "go.pricing.body": "Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.", + "go.pricing.body": + "Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle e modelli gratuiti", "go.graph.go": "Go", @@ -426,6 +427,7 @@ export const dict = { "workspace.nav.settings": "Impostazioni", "workspace.home.banner.beforeLink": "Modelli ottimizzati e affidabili per agenti di coding.", + "workspace.lite.banner.beforeLink": "Modelli di coding a basso costo per tutti.", "workspace.home.billing.loading": "Caricamento...", "workspace.home.billing.enable": "Abilita fatturazione", "workspace.home.billing.currentBalance": "Saldo attuale", @@ -628,7 +630,6 @@ export const dict = { "workspace.lite.time.minute": "minuto", "workspace.lite.time.minutes": "minuti", "workspace.lite.time.fewSeconds": "pochi secondi", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Sei abbonato a OpenCode Go.", "workspace.lite.subscription.manage": "Gestisci Abbonamento", "workspace.lite.subscription.rollingUsage": "Utilizzo Continuativo", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index e74bc666b6..304233c04e 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -260,7 +260,8 @@ export const dict = { "go.cta.text": "Goを購読する", "go.cta.price": "$10/月", "go.cta.promo": "初月 $5", - "go.pricing.body": "どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。", + "go.pricing.body": + "どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。", "go.graph.free": "無料", "go.graph.freePill": "Big Pickleと無料モデル", "go.graph.go": "Go", @@ -425,6 +426,7 @@ export const dict = { "workspace.nav.settings": "設定", "workspace.home.banner.beforeLink": "コーディングエージェント向けに信頼性の高い最適化されたモデル。", + "workspace.lite.banner.beforeLink": "誰でも使える低コストコーディングモデル。", "workspace.home.billing.loading": "読み込み中...", "workspace.home.billing.enable": "課金を有効にする", "workspace.home.billing.currentBalance": "現在の残高", @@ -628,7 +630,6 @@ export const dict = { "workspace.lite.time.minute": "分", "workspace.lite.time.minutes": "分", "workspace.lite.time.fewSeconds": "数秒", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "あなたは OpenCode Go を購読しています。", "workspace.lite.subscription.manage": "サブスクリプションの管理", "workspace.lite.subscription.rollingUsage": "ローリング利用量", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index ca7b4be93b..3ba155341c 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -257,7 +257,8 @@ export const dict = { "go.cta.text": "Go 구독하기", "go.cta.price": "$10/월", "go.cta.promo": "첫 달 $5", - "go.pricing.body": "어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.", + "go.pricing.body": + "어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.", "go.graph.free": "무료", "go.graph.freePill": "Big Pickle 및 무료 모델", "go.graph.go": "Go", @@ -419,6 +420,7 @@ export const dict = { "workspace.nav.settings": "설정", "workspace.home.banner.beforeLink": "코딩 에이전트를 위한 신뢰할 수 있고 최적화된 모델.", + "workspace.lite.banner.beforeLink": "모두를 위한 저비용 코딩 모델.", "workspace.home.billing.loading": "로드 중...", "workspace.home.billing.enable": "결제 활성화", "workspace.home.billing.currentBalance": "현재 잔액", @@ -620,7 +622,6 @@ export const dict = { "workspace.lite.time.minute": "분", "workspace.lite.time.minutes": "분", "workspace.lite.time.fewSeconds": "몇 초", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "현재 OpenCode Go를 구독 중입니다.", "workspace.lite.subscription.manage": "구독 관리", "workspace.lite.subscription.rollingUsage": "롤링 사용량", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 269db2438f..45904ab7b6 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -261,7 +261,8 @@ export const dict = { "go.cta.text": "Abonner på Go", "go.cta.price": "$10/måned", "go.cta.promo": "$5 første måned", - "go.pricing.body": "Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.", + "go.pricing.body": + "Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", @@ -300,7 +301,8 @@ export const dict = { "go.problem.item3": "Bygget for så mange programmerere som mulig", "go.problem.item4": "Inkluderer GLM-5, Kimi K2.5 og MiniMax M2.5", "go.how.title": "Hvordan Go fungerer", - "go.how.body": "Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.", + "go.how.body": + "Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.", "go.how.step1.title": "Opprett en konto", "go.how.step1.beforeLink": "følg", "go.how.step1.link": "oppsettsinstruksjonene", @@ -424,6 +426,7 @@ export const dict = { "workspace.nav.settings": "Innstillinger", "workspace.home.banner.beforeLink": "Pålitelige optimaliserte modeller for kodeagenter.", + "workspace.lite.banner.beforeLink": "Lavkost kodemodeller for alle.", "workspace.home.billing.loading": "Laster...", "workspace.home.billing.enable": "Aktiver fakturering", "workspace.home.billing.currentBalance": "Gjeldende saldo", @@ -626,7 +629,6 @@ export const dict = { "workspace.lite.time.minute": "minutt", "workspace.lite.time.minutes": "minutter", "workspace.lite.time.fewSeconds": "noen få sekunder", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Du abonnerer på OpenCode Go.", "workspace.lite.subscription.manage": "Administrer abonnement", "workspace.lite.subscription.rollingUsage": "Løpende bruk", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index f8d042af4c..78ad69aeea 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -262,7 +262,8 @@ export const dict = { "go.cta.text": "Zasubskrybuj Go", "go.cta.price": "$10/miesiąc", "go.cta.promo": "$5 pierwszy miesiąc", - "go.pricing.body": "Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.", + "go.pricing.body": + "Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.", "go.graph.free": "Darmowe", "go.graph.freePill": "Big Pickle i darmowe modele", "go.graph.go": "Go", @@ -301,7 +302,8 @@ export const dict = { "go.problem.item3": "Stworzony dla jak największej liczby programistów", "go.problem.item4": "Zawiera GLM-5, Kimi K2.5 i MiniMax M2.5", "go.how.title": "Jak działa Go", - "go.how.body": "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.", + "go.how.body": + "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.", "go.how.step1.title": "Załóż konto", "go.how.step1.beforeLink": "postępuj zgodnie z", "go.how.step1.link": "instrukcją konfiguracji", @@ -425,6 +427,7 @@ export const dict = { "workspace.nav.settings": "Ustawienia", "workspace.home.banner.beforeLink": "Niezawodne, zoptymalizowane modele dla agentów kodujących.", + "workspace.lite.banner.beforeLink": "Niskokosztowe modele kodowania dla każdego.", "workspace.home.billing.loading": "Ładowanie...", "workspace.home.billing.enable": "Włącz rozliczenia", "workspace.home.billing.currentBalance": "Aktualne saldo", @@ -627,7 +630,6 @@ export const dict = { "workspace.lite.time.minute": "minuta", "workspace.lite.time.minutes": "minut(y)", "workspace.lite.time.fewSeconds": "kilka sekund", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Subskrybujesz OpenCode Go.", "workspace.lite.subscription.manage": "Zarządzaj subskrypcją", "workspace.lite.subscription.rollingUsage": "Użycie kroczące", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index a429f37ecb..b63b5d1b1e 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -265,7 +265,8 @@ export const dict = { "go.cta.text": "Подписаться на Go", "go.cta.price": "$10/месяц", "go.cta.promo": "$5 первый месяц", - "go.pricing.body": "Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.", + "go.pricing.body": + "Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.", "go.graph.free": "Бесплатно", "go.graph.freePill": "Big Pickle и бесплатные модели", "go.graph.go": "Go", @@ -305,7 +306,8 @@ export const dict = { "go.problem.item3": "Создан для максимального числа программистов", "go.problem.item4": "Включает GLM-5, Kimi K2.5 и MiniMax M2.5", "go.how.title": "Как работает Go", - "go.how.body": "Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.", + "go.how.body": + "Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.", "go.how.step1.title": "Создайте аккаунт", "go.how.step1.beforeLink": "следуйте", "go.how.step1.link": "инструкциям по настройке", @@ -430,6 +432,7 @@ export const dict = { "workspace.nav.settings": "Настройки", "workspace.home.banner.beforeLink": "Надежные оптимизированные модели для кодинг-агентов.", + "workspace.lite.banner.beforeLink": "Недорогие модели для кодинга, доступные каждому.", "workspace.home.billing.loading": "Загрузка...", "workspace.home.billing.enable": "Включить оплату", "workspace.home.billing.currentBalance": "Текущий баланс", @@ -633,7 +636,6 @@ export const dict = { "workspace.lite.time.minute": "минута", "workspace.lite.time.minutes": "минут", "workspace.lite.time.fewSeconds": "несколько секунд", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "Вы подписаны на OpenCode Go.", "workspace.lite.subscription.manage": "Управление подпиской", "workspace.lite.subscription.rollingUsage": "Скользящее использование", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index bb1e3e344c..b6e29ba022 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -422,6 +422,7 @@ export const dict = { "workspace.nav.settings": "การตั้งค่า", "workspace.home.banner.beforeLink": "โมเดลที่เชื่อถือได้และปรับแต่งแล้วสำหรับเอเจนต์เขียนโค้ด", + "workspace.lite.banner.beforeLink": "โมเดลเขียนโค้ดต้นทุนต่ำสำหรับทุกคน", "workspace.home.billing.loading": "กำลังโหลด...", "workspace.home.billing.enable": "เปิดใช้งานการเรียกเก็บเงิน", "workspace.home.billing.currentBalance": "ยอดคงเหลือปัจจุบัน", @@ -624,7 +625,6 @@ export const dict = { "workspace.lite.time.minute": "นาที", "workspace.lite.time.minutes": "นาที", "workspace.lite.time.fewSeconds": "ไม่กี่วินาที", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "คุณได้สมัครสมาชิก OpenCode Go แล้ว", "workspace.lite.subscription.manage": "จัดการการสมัครสมาชิก", "workspace.lite.subscription.rollingUsage": "การใช้งานแบบหมุนเวียน", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 5f38d4e5cf..e2e5c4985c 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -429,6 +429,7 @@ export const dict = { "workspace.nav.settings": "Ayarlar", "workspace.home.banner.beforeLink": "Kodlama ajanları için güvenilir optimize edilmiş modeller.", + "workspace.lite.banner.beforeLink": "Herkes için düşük maliyetli kodlama modelleri.", "workspace.home.billing.loading": "Yükleniyor...", "workspace.home.billing.enable": "Faturalandırmayı etkinleştir", "workspace.home.billing.currentBalance": "Mevcut bakiye", @@ -631,7 +632,6 @@ export const dict = { "workspace.lite.time.minute": "dakika", "workspace.lite.time.minutes": "dakika", "workspace.lite.time.fewSeconds": "birkaç saniye", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "OpenCode Go abonesisiniz.", "workspace.lite.subscription.manage": "Aboneliği Yönet", "workspace.lite.subscription.rollingUsage": "Devam Eden Kullanım", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index a8d695ffd7..f75d553197 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -405,6 +405,7 @@ export const dict = { "workspace.nav.settings": "设置", "workspace.home.banner.beforeLink": "可靠、优化的编程代理模型。", + "workspace.lite.banner.beforeLink": "低成本编码模型,人人可用。", "workspace.home.billing.loading": "加载中...", "workspace.home.billing.enable": "启用计费", "workspace.home.billing.currentBalance": "当前余额", @@ -604,7 +605,6 @@ export const dict = { "workspace.lite.time.minute": "分钟", "workspace.lite.time.minutes": "分钟", "workspace.lite.time.fewSeconds": "几秒钟", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "您已订阅 OpenCode Go。", "workspace.lite.subscription.manage": "管理订阅", "workspace.lite.subscription.rollingUsage": "滚动用量", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 964eb70893..3919a9d739 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -240,8 +240,7 @@ export const dict = { "zen.privacy.exceptionsLink": "以下例外情況", "go.title": "OpenCode Go | 低成本全民編碼模型", - "go.meta.description": - "Go 首月 $5,之後 $10/月,提供對 GLM-5、Kimi K2.5 和 MiniMax M2.5 的 5 小時充裕請求額度。", + "go.meta.description": "Go 首月 $5,之後 $10/月,提供對 GLM-5、Kimi K2.5 和 MiniMax M2.5 的 5 小時充裕請求額度。", "go.hero.title": "低成本全民編碼模型", "go.hero.body": "Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。", @@ -406,6 +405,7 @@ export const dict = { "workspace.nav.settings": "設定", "workspace.home.banner.beforeLink": "編碼代理的可靠最佳化模型。", + "workspace.lite.banner.beforeLink": "低成本編碼模型,人人可用。", "workspace.home.billing.loading": "載入中...", "workspace.home.billing.enable": "啟用帳務", "workspace.home.billing.currentBalance": "目前餘額", @@ -605,7 +605,6 @@ export const dict = { "workspace.lite.time.minute": "分鐘", "workspace.lite.time.minutes": "分鐘", "workspace.lite.time.fewSeconds": "幾秒", - "workspace.lite.title": "OpenCode Go", "workspace.lite.subscription.message": "您已訂閱 OpenCode Go。", "workspace.lite.subscription.manage": "管理訂閱", "workspace.lite.subscription.rollingUsage": "滾動使用量", diff --git a/packages/console/app/src/routes/workspace/[id]/go/index.tsx b/packages/console/app/src/routes/workspace/[id]/go/index.tsx index 116938a121..fb89e3c702 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/index.tsx @@ -1,8 +1,27 @@ +import { IconGo } from "~/component/icon" +import { useI18n } from "~/context/i18n" +import { useLanguage } from "~/context/language" import { LiteSection } from "./lite-section" export default function () { + const i18n = useI18n() + const language = useLanguage() + return ( <div data-page="workspace-[id]"> + <section data-component="header-section"> + <IconGo /> + <p> + <span> + {i18n.t("workspace.lite.banner.beforeLink")}{" "} + <a target="_blank" href={language.route("/docs/go")}> + {i18n.t("common.learnMore")} + </a> + . + </span> + </p> + </section> + <div data-slot="sections"> <LiteSection /> </div> diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index e450cf9448..ede8709215 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -170,9 +170,6 @@ export function LiteSection() { <> <Show when={isBlack()}> <section class={styles.root}> - <div data-slot="section-title"> - <h2>{i18n.t("workspace.lite.title")}</h2> - </div> <p data-slot="other-message">{i18n.t("workspace.lite.black.message")}</p> </section> </Show> @@ -180,7 +177,6 @@ export function LiteSection() { {(sub) => ( <section class={styles.root}> <div data-slot="section-title"> - <h2>{i18n.t("workspace.lite.title")}</h2> <div data-slot="title-row"> <p>{i18n.t("workspace.lite.subscription.message")}</p> <button @@ -260,17 +256,11 @@ export function LiteSection() { </Show> <Show when={!isBlack() && lite() && !lite()!.mine}> <section class={styles.root}> - <div data-slot="section-title"> - <h2>{i18n.t("workspace.lite.title")}</h2> - </div> <p data-slot="other-message">{i18n.t("workspace.lite.other.message")}</p> </section> </Show> <Show when={!isBlack() && lite() === null}> <section class={styles.root}> - <div data-slot="section-title"> - <h2>{i18n.t("workspace.lite.title")}</h2> - </div> <p data-slot="promo-description"> <For each={i18n diff --git a/packages/console/app/src/routes/workspace/[id]/index.tsx b/packages/console/app/src/routes/workspace/[id]/index.tsx index ca3c2bc0ca..b5c597b121 100644 --- a/packages/console/app/src/routes/workspace/[id]/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/index.tsx @@ -1,10 +1,10 @@ -import { Match, Show, Switch, createMemo } from "solid-js" +import { Show, createMemo } from "solid-js" import { createStore } from "solid-js/store" import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router" import { NewUserSection } from "./new-user-section" import { ModelSection } from "./model-section" import { ProviderSection } from "./provider-section" -import { IconLogo } from "~/component/icon" +import { IconZen } from "~/component/icon" import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" @@ -34,7 +34,7 @@ export default function () { return ( <div data-page="workspace-[id]"> <section data-component="header-section"> - <IconLogo /> + <IconZen /> <p> <span> {i18n.t("workspace.home.banner.beforeLink")}{" "} From 78de287bcc3e2695662e043cf70734a36f5caeb3 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 17:12:05 -0400 Subject: [PATCH 103/200] wip: zen --- packages/console/core/src/billing.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index 77657fc63c..cbb3f2d74e 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -213,9 +213,7 @@ export namespace Billing { enabled: true, }, payment_method_options: { - alipay: { - setup_future_usage: "off_session", - }, + alipay: {}, card: { setup_future_usage: "on_session", }, From 8569fc1f0eff173febf8608e5264be48e8da88dc Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 17:19:26 -0400 Subject: [PATCH 104/200] docs: zen update models --- packages/web/src/content/docs/zen.mdx | 82 +++++++++++++++------------ 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 330f90014d..ed2be57e7c 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -62,43 +62,45 @@ You are charged per request and you can add credits to your account. You can also access our models through the following API endpoints. -| Model | Model ID | Endpoint | AI SDK Package | -| ------------------- | ------------------- | -------------------------------------------------- | --------------------------- | -| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | -| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | -| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | -| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Package | +| --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | +| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | +| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo V2 Flash Free | mimo-v2-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | The [model id](/docs/config/#models) in your OpenCode config uses the format `opencode/<model-id>`. For example, for GPT 5.3 Codex, you would @@ -123,6 +125,8 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | +| MiMo V2 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - | @@ -173,6 +177,8 @@ Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don The free models: - MiniMax M2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. +- MiMo V2 Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. +- Nemotron 3 Super Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. <a href={email}>Contact us</a> if you have any questions. @@ -217,6 +223,8 @@ All our models are hosted in the US. Our providers follow a zero-retention polic - Big Pickle: During its free period, collected data may be used to improve the model. - MiniMax M2.5 Free: During its free period, collected data may be used to improve the model. +- MiniMax M2.5 Free: During its free period, collected data may be used to improve the model. +- Nemotron 3 Super Free: During its free period, collected data may be used to improve the model. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). From aa81c1c4cb1f1a89828584e0fba22f35b8bc774b Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 18:09:39 -0400 Subject: [PATCH 105/200] docs: go pricing --- packages/web/src/content/docs/go.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 5976f1a358..d408b65b1e 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -7,7 +7,7 @@ import config from "../../../config.mjs" export const console = config.console export const email = `mailto:${config.email}` -OpenCode Go is a low cost **$10/month** subscription that gives you reliable access to popular open coding models. +OpenCode Go is a low cost subscription — **$5 for your first month**, then **$10/month** — that gives you reliable access to popular open coding models. :::note OpenCode Go is currently in beta. @@ -43,7 +43,7 @@ To fix this, we did a couple of things: 3. Finally, we benchmarked the combination of the model/provider and came up with a list that we feel good recommending. -OpenCode Go gives you access to these models for **$10/month**. +OpenCode Go gives you access to these models for **$5 for your first month**, then **$10/month**. --- From cc18fa599ccf3d86abb34b5b069ec29c4dd64ed2 Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 18:50:49 -0400 Subject: [PATCH 106/200] wip: zen --- packages/console/core/src/billing.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index cbb3f2d74e..66a8858281 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -218,7 +218,7 @@ export namespace Billing { setup_future_usage: "on_session", }, }, - payment_method_types: ["card", "alipay"], + payment_method_types: ["card" /*, "alipay"*/], //payment_method_data: { // allow_redisplay: "always", //}, @@ -269,7 +269,7 @@ export namespace Billing { customer_email: email!, }), currency: "usd", - payment_method_types: ["card", "alipay"], + payment_method_types: ["card" /*, "alipay"*/], tax_id_collection: { enabled: true, }, From 4e73473119e4f787f485ec859035d7047115cfcc Mon Sep 17 00:00:00 2001 From: Frank <frank@anoma.ly> Date: Wed, 11 Mar 2026 19:00:05 -0400 Subject: [PATCH 107/200] wip: zen --- packages/console/core/src/billing.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index 66a8858281..cbb3f2d74e 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -218,7 +218,7 @@ export namespace Billing { setup_future_usage: "on_session", }, }, - payment_method_types: ["card" /*, "alipay"*/], + payment_method_types: ["card", "alipay"], //payment_method_data: { // allow_redisplay: "always", //}, @@ -269,7 +269,7 @@ export namespace Billing { customer_email: email!, }), currency: "usd", - payment_method_types: ["card" /*, "alipay"*/], + payment_method_types: ["card", "alipay"], tax_id_collection: { enabled: true, }, From cb6746567554f6d48a635530a224f94a915bced4 Mon Sep 17 00:00:00 2001 From: Kit Langton <kit.langton@gmail.com> Date: Wed, 11 Mar 2026 19:16:56 -0400 Subject: [PATCH 108/200] feat(id): brand SessionID through Drizzle and Zod schemas (#16953) --- packages/opencode/src/cli/cmd/export.ts | 7 ++- packages/opencode/src/cli/cmd/github.ts | 3 +- packages/opencode/src/cli/cmd/import.ts | 12 +++- packages/opencode/src/cli/cmd/session.ts | 6 +- packages/opencode/src/cli/cmd/tui/event.ts | 3 +- packages/opencode/src/command/index.ts | 3 +- packages/opencode/src/permission/index.ts | 5 +- packages/opencode/src/permission/next.ts | 5 +- packages/opencode/src/question/index.ts | 9 +-- .../opencode/src/server/routes/session.ts | 37 +++++++------ packages/opencode/src/session/compaction.ts | 9 +-- packages/opencode/src/session/index.ts | 55 ++++++++++--------- packages/opencode/src/session/message-v2.ts | 15 ++--- packages/opencode/src/session/message.ts | 3 +- packages/opencode/src/session/processor.ts | 3 +- packages/opencode/src/session/prompt.ts | 15 ++--- packages/opencode/src/session/revert.ts | 5 +- packages/opencode/src/session/schema.ts | 17 ++++++ packages/opencode/src/session/session.sql.ts | 9 ++- packages/opencode/src/session/status.ts | 9 +-- packages/opencode/src/session/summary.ts | 7 ++- packages/opencode/src/session/todo.ts | 7 ++- packages/opencode/src/share/share-next.ts | 11 ++-- packages/opencode/src/tool/plan.ts | 3 +- packages/opencode/src/tool/task.ts | 3 +- packages/opencode/src/tool/tool.ts | 3 +- .../opencode/test/cli/github-action.test.ts | 13 +++-- .../opencode/test/memory/abort-leak.test.ts | 3 +- .../opencode/test/permission/next.test.ts | 23 ++++---- .../opencode/test/question/question.test.ts | 19 ++++--- packages/opencode/test/session/llm.test.ts | 9 +-- .../opencode/test/session/message-v2.test.ts | 3 +- .../test/session/structured-output.test.ts | 7 ++- .../test/storage/json-migration.test.ts | 13 +++-- .../opencode/test/tool/apply_patch.test.ts | 3 +- packages/opencode/test/tool/bash.test.ts | 3 +- packages/opencode/test/tool/edit.test.ts | 3 +- .../test/tool/external-directory.test.ts | 3 +- packages/opencode/test/tool/grep.test.ts | 3 +- packages/opencode/test/tool/question.test.ts | 3 +- packages/opencode/test/tool/read.test.ts | 3 +- packages/opencode/test/tool/skill.test.ts | 3 +- packages/opencode/test/tool/webfetch.test.ts | 3 +- packages/opencode/test/tool/write.test.ts | 3 +- 44 files changed, 226 insertions(+), 158 deletions(-) create mode 100644 packages/opencode/src/session/schema.ts diff --git a/packages/opencode/src/cli/cmd/export.ts b/packages/opencode/src/cli/cmd/export.ts index 20c95bf87e..4088b4818d 100644 --- a/packages/opencode/src/cli/cmd/export.ts +++ b/packages/opencode/src/cli/cmd/export.ts @@ -1,5 +1,6 @@ import type { Argv } from "yargs" import { Session } from "../../session" +import { SessionID } from "../../session/schema" import { cmd } from "./cmd" import { bootstrap } from "../bootstrap" import { UI } from "../ui" @@ -17,7 +18,7 @@ export const ExportCommand = cmd({ }, handler: async (args) => { await bootstrap(process.cwd(), async () => { - let sessionID = args.sessionID + let sessionID = args.sessionID ? SessionID.make(args.sessionID) : undefined process.stderr.write(`Exporting session: ${sessionID ?? "latest"}\n`) if (!sessionID) { @@ -58,7 +59,7 @@ export const ExportCommand = cmd({ throw new UI.CancelledError() } - sessionID = selectedSession as string + sessionID = selectedSession prompts.outro("Exporting session...", { output: process.stderr, @@ -67,7 +68,7 @@ export const ExportCommand = cmd({ try { const sessionInfo = await Session.get(sessionID!) - const messages = await Session.messages({ sessionID: sessionID! }) + const messages = await Session.messages({ sessionID: sessionInfo.id }) const exportData = { info: sessionInfo, diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index a58151308c..ad893ce4f3 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -22,6 +22,7 @@ import { ModelsDev } from "../../provider/models" import { Instance } from "@/project/instance" import { bootstrap } from "../bootstrap" import { Session } from "../../session" +import type { SessionID } from "../../session/schema" import { Identifier } from "../../id/id" import { Provider } from "../../provider/provider" import { Bus } from "../../bus" @@ -481,7 +482,7 @@ export const GithubRunCommand = cmd({ let octoRest: Octokit let octoGraph: typeof graphql let gitConfig: string - let session: { id: string; title: string; version: string } + let session: { id: SessionID; title: string; version: string } let shareId: string | undefined let exitCode = 0 type PromptFiles = Awaited<ReturnType<typeof getUserPrompt>>["promptFiles"] diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index cfea5fdbb3..f06b8f39fe 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2" import { Session } from "../../session" +import { SessionID } from "../../session/schema" import { cmd } from "./cmd" import { bootstrap } from "../bootstrap" import { Database } from "../../storage/db" @@ -152,7 +153,12 @@ export const ImportCommand = cmd({ return } - const row = Session.toRow({ ...exportData.info, projectID: Instance.project.id }) + const row = Session.toRow({ + ...exportData.info, + id: SessionID.make(exportData.info.id), + parentID: exportData.info.parentID ? SessionID.make(exportData.info.parentID) : undefined, + projectID: Instance.project.id, + }) Database.use((db) => db .insert(SessionTable) @@ -167,7 +173,7 @@ export const ImportCommand = cmd({ .insert(MessageTable) .values({ id: msg.info.id, - session_id: exportData.info.id, + session_id: row.id, time_created: msg.info.time?.created ?? Date.now(), data: msg.info, }) @@ -182,7 +188,7 @@ export const ImportCommand = cmd({ .values({ id: part.id, message_id: msg.info.id, - session_id: exportData.info.id, + session_id: row.id, data: part, }) .onConflictDoNothing() diff --git a/packages/opencode/src/cli/cmd/session.ts b/packages/opencode/src/cli/cmd/session.ts index 84840392a6..8acd7480c9 100644 --- a/packages/opencode/src/cli/cmd/session.ts +++ b/packages/opencode/src/cli/cmd/session.ts @@ -1,6 +1,7 @@ import type { Argv } from "yargs" import { cmd } from "./cmd" import { Session } from "../../session" +import { SessionID } from "../../session/schema" import { bootstrap } from "../bootstrap" import { UI } from "../ui" import { Locale } from "../../util/locale" @@ -57,13 +58,14 @@ export const SessionDeleteCommand = cmd({ }, handler: async (args) => { await bootstrap(process.cwd(), async () => { + const sessionID = SessionID.make(args.sessionID) try { - await Session.get(args.sessionID) + await Session.get(sessionID) } catch { UI.error(`Session not found: ${args.sessionID}`) process.exit(1) } - await Session.remove(args.sessionID) + await Session.remove(sessionID) UI.println(UI.Style.TEXT_SUCCESS_BOLD + `Session ${args.sessionID} deleted` + UI.Style.TEXT_NORMAL) }) }, diff --git a/packages/opencode/src/cli/cmd/tui/event.ts b/packages/opencode/src/cli/cmd/tui/event.ts index 9466ae54f2..b2e4b92c55 100644 --- a/packages/opencode/src/cli/cmd/tui/event.ts +++ b/packages/opencode/src/cli/cmd/tui/event.ts @@ -1,5 +1,6 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" +import { SessionID } from "@/session/schema" import z from "zod" export const TuiEvent = { @@ -42,7 +43,7 @@ export const TuiEvent = { SessionSelect: BusEvent.define( "tui.session.select", z.object({ - sessionID: z.string().regex(/^ses/).describe("Session ID to navigate to"), + sessionID: SessionID.zod.describe("Session ID to navigate to"), }), ), } diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index dce7ac8bbc..3f9f25e1dc 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -1,4 +1,5 @@ import { BusEvent } from "@/bus/bus-event" +import { SessionID } from "@/session/schema" import z from "zod" import { Config } from "../config/config" import { Instance } from "../project/instance" @@ -14,7 +15,7 @@ export namespace Command { "command.executed", z.object({ name: z.string(), - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, arguments: z.string(), messageID: Identifier.schema("message"), }), diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index f1cd43fdbe..16a94ec54d 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -1,5 +1,6 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" +import { SessionID } from "@/session/schema" import z from "zod" import { Log } from "../util/log" import { Identifier } from "../id/id" @@ -24,7 +25,7 @@ export namespace Permission { id: z.string(), type: z.string(), pattern: z.union([z.string(), z.array(z.string())]).optional(), - sessionID: z.string(), + sessionID: SessionID.zod, messageID: z.string(), callID: z.string().optional(), message: z.string(), @@ -43,7 +44,7 @@ export namespace Permission { Replied: BusEvent.define( "permission.replied", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, permissionID: z.string(), response: z.string(), }), diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 4e47e43c8f..6bfe0035ba 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -2,6 +2,7 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { Config } from "@/config/config" import { Identifier } from "@/id/id" +import { SessionID } from "@/session/schema" import { Instance } from "@/project/instance" import { Database, eq } from "@/storage/db" import { PermissionTable } from "@/session/session.sql" @@ -69,7 +70,7 @@ export namespace PermissionNext { export const Request = z .object({ id: Identifier.schema("permission"), - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, permission: z.string(), patterns: z.string().array(), metadata: z.record(z.string(), z.any()), @@ -100,7 +101,7 @@ export namespace PermissionNext { Replied: BusEvent.define( "permission.replied", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, requestID: z.string(), reply: Reply, }), diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index c93b74b9a4..ba1fc66506 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -1,6 +1,7 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { Identifier } from "@/id/id" +import { SessionID } from "@/session/schema" import { Instance } from "@/project/instance" import { Log } from "@/util/log" import z from "zod" @@ -34,7 +35,7 @@ export namespace Question { export const Request = z .object({ id: Identifier.schema("question"), - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, questions: z.array(Info).describe("Questions to ask"), tool: z .object({ @@ -65,7 +66,7 @@ export namespace Question { Replied: BusEvent.define( "question.replied", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, requestID: z.string(), answers: z.array(Answer), }), @@ -73,7 +74,7 @@ export namespace Question { Rejected: BusEvent.define( "question.rejected", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, requestID: z.string(), }), ), @@ -95,7 +96,7 @@ export namespace Question { }) export async function ask(input: { - sessionID: string + sessionID: SessionID questions: Info[] tool?: { messageID: string; callID: string } }): Promise<Answer[]> { diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 12938aeaba..8e465d7ec2 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -1,6 +1,7 @@ import { Hono } from "hono" import { stream } from "hono/streaming" import { describeRoute, validator, resolver } from "hono-openapi" +import { SessionID } from "@/session/schema" import z from "zod" import { Session } from "../../session" import { MessageV2 } from "../../session/message-v2" @@ -173,7 +174,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), async (c) => { @@ -258,7 +259,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, }), ), validator( @@ -309,7 +310,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), validator("json", Session.initialize.schema.omit({ sessionID: true })), @@ -372,7 +373,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, }), ), async (c) => { @@ -401,7 +402,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, }), ), async (c) => { @@ -502,7 +503,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), validator( @@ -561,7 +562,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), validator( @@ -605,7 +606,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, messageID: z.string().meta({ description: "Message ID" }), }), ), @@ -640,7 +641,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, messageID: z.string().meta({ description: "Message ID" }), }), ), @@ -674,7 +675,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, messageID: z.string().meta({ description: "Message ID" }), partID: z.string().meta({ description: "Part ID" }), }), @@ -709,7 +710,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, messageID: z.string().meta({ description: "Message ID" }), partID: z.string().meta({ description: "Part ID" }), }), @@ -753,7 +754,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })), @@ -785,7 +786,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })), @@ -825,7 +826,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })), @@ -857,7 +858,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string().meta({ description: "Session ID" }), + sessionID: SessionID.zod, }), ), validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })), @@ -889,7 +890,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, }), ), validator("json", SessionRevert.RevertInput.omit({ sessionID: true })), @@ -924,7 +925,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, }), ), async (c) => { @@ -955,7 +956,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, permissionID: z.string(), }), ), diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 79884d641e..d26c3bb6b9 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -2,6 +2,7 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { Session } from "." import { Identifier } from "../id/id" +import { SessionID } from "./schema" import { Instance } from "../project/instance" import { Provider } from "../provider/provider" import { MessageV2 } from "./message-v2" @@ -22,7 +23,7 @@ export namespace SessionCompaction { Compacted: BusEvent.define( "session.compacted", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, }), ), } @@ -55,7 +56,7 @@ export namespace SessionCompaction { // goes backwards through parts until there are 40_000 tokens worth of tool // calls. then erases output of previous tool calls. idea is to throw away old // tool calls that are no longer relevant. - export async function prune(input: { sessionID: string }) { + export async function prune(input: { sessionID: SessionID }) { const config = await Config.get() if (config.compaction?.prune === false) return log.info("pruning") @@ -101,7 +102,7 @@ export namespace SessionCompaction { export async function process(input: { parentID: string messages: MessageV2.WithParts[] - sessionID: string + sessionID: SessionID abort: AbortSignal auto: boolean overflow?: boolean @@ -295,7 +296,7 @@ When constructing the summary, try to stick to this template: export const create = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, agent: z.string(), model: z.object({ providerID: z.string(), diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index ff499fe2e7..76b550d309 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -24,6 +24,7 @@ import { Command } from "../command" import { Snapshot } from "@/snapshot" import { WorkspaceContext } from "../control-plane/workspace-context" import { ProjectID } from "../project/schema" +import { SessionID } from "./schema" import type { Provider } from "@/provider/provider" import { PermissionNext } from "@/permission/next" @@ -119,12 +120,12 @@ export namespace Session { export const Info = z .object({ - id: Identifier.schema("session"), + id: SessionID.zod, slug: z.string(), projectID: ProjectID.zod, workspaceID: z.string().optional(), directory: z.string(), - parentID: Identifier.schema("session").optional(), + parentID: SessionID.zod.optional(), summary: z .object({ additions: z.number(), @@ -201,14 +202,14 @@ export namespace Session { Diff: BusEvent.define( "session.diff", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, diff: Snapshot.FileDiff.array(), }), ), Error: BusEvent.define( "session.error", z.object({ - sessionID: z.string().optional(), + sessionID: SessionID.zod.optional(), error: MessageV2.Assistant.shape.error, }), ), @@ -217,7 +218,7 @@ export namespace Session { export const create = fn( z .object({ - parentID: Identifier.schema("session").optional(), + parentID: SessionID.zod.optional(), title: z.string().optional(), permission: Info.shape.permission, workspaceID: Identifier.schema("workspace").optional(), @@ -236,7 +237,7 @@ export namespace Session { export const fork = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, messageID: Identifier.schema("message").optional(), }), async (input) => { @@ -277,7 +278,7 @@ export namespace Session { }, ) - export const touch = fn(Identifier.schema("session"), async (sessionID) => { + export const touch = fn(SessionID.zod, async (sessionID) => { const now = Date.now() Database.use((db) => { const row = db @@ -293,15 +294,15 @@ export namespace Session { }) export async function createNext(input: { - id?: string + id?: SessionID title?: string - parentID?: string + parentID?: SessionID workspaceID?: string directory: string permission?: PermissionNext.Ruleset }) { const result: Info = { - id: Identifier.descending("session", input.id), + id: SessionID.descending(input.id), slug: Slug.create(), version: Installation.VERSION, projectID: Instance.project.id, @@ -342,13 +343,13 @@ export namespace Session { return path.join(base, [input.time.created, input.slug].join("-") + ".md") } - export const get = fn(Identifier.schema("session"), async (id) => { + export const get = fn(SessionID.zod, async (id) => { const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) if (!row) throw new NotFoundError({ message: `Session not found: ${id}` }) return fromRow(row) }) - export const share = fn(Identifier.schema("session"), async (id) => { + export const share = fn(SessionID.zod, async (id) => { const cfg = await Config.get() if (cfg.share === "disabled") { throw new Error("Sharing is disabled in configuration") @@ -364,7 +365,7 @@ export namespace Session { return share }) - export const unshare = fn(Identifier.schema("session"), async (id) => { + export const unshare = fn(SessionID.zod, async (id) => { // Use ShareNext to remove the share (same as share function uses ShareNext to create) const { ShareNext } = await import("@/share/share-next") await ShareNext.remove(id) @@ -378,7 +379,7 @@ export namespace Session { export const setTitle = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, title: z.string(), }), async (input) => { @@ -399,7 +400,7 @@ export namespace Session { export const setArchived = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, time: z.number().optional(), }), async (input) => { @@ -420,7 +421,7 @@ export namespace Session { export const setPermission = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, permission: PermissionNext.Ruleset, }), async (input) => { @@ -441,7 +442,7 @@ export namespace Session { export const setRevert = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, revert: Info.shape.revert, summary: Info.shape.summary, }), @@ -467,7 +468,7 @@ export namespace Session { }, ) - export const clearRevert = fn(Identifier.schema("session"), async (sessionID) => { + export const clearRevert = fn(SessionID.zod, async (sessionID) => { return Database.use((db) => { const row = db .update(SessionTable) @@ -487,7 +488,7 @@ export namespace Session { export const setSummary = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, summary: Info.shape.summary, }), async (input) => { @@ -511,7 +512,7 @@ export namespace Session { }, ) - export const diff = fn(Identifier.schema("session"), async (sessionID) => { + export const diff = fn(SessionID.zod, async (sessionID) => { try { return await Storage.read<Snapshot.FileDiff[]>(["session_diff", sessionID]) } catch { @@ -521,7 +522,7 @@ export namespace Session { export const messages = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, limit: z.number().optional(), }), async (input) => { @@ -647,7 +648,7 @@ export namespace Session { } } - export const children = fn(Identifier.schema("session"), async (parentID) => { + export const children = fn(SessionID.zod, async (parentID) => { const project = Instance.project const rows = Database.use((db) => db @@ -659,7 +660,7 @@ export namespace Session { return rows.map(fromRow) }) - export const remove = fn(Identifier.schema("session"), async (sessionID) => { + export const remove = fn(SessionID.zod, async (sessionID) => { const project = Instance.project try { const session = await get(sessionID) @@ -705,7 +706,7 @@ export namespace Session { export const removeMessage = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, messageID: Identifier.schema("message"), }), async (input) => { @@ -727,7 +728,7 @@ export namespace Session { export const removePart = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, messageID: Identifier.schema("message"), partID: Identifier.schema("part"), }), @@ -775,7 +776,7 @@ export namespace Session { export const updatePartDelta = fn( z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, messageID: z.string(), partID: z.string(), field: z.string(), @@ -873,7 +874,7 @@ export namespace Session { export const initialize = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, modelID: z.string(), providerID: z.string(), messageID: Identifier.schema("message"), diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 5b4e7bdbc0..43d735e4bd 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,4 +1,5 @@ import { BusEvent } from "@/bus/bus-event" +import { SessionID } from "./schema" import z from "zod" import { NamedError } from "@opencode-ai/util/error" import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" @@ -79,7 +80,7 @@ export namespace MessageV2 { const PartBase = z.object({ id: z.string(), - sessionID: z.string(), + sessionID: SessionID.zod, messageID: z.string(), }) @@ -344,7 +345,7 @@ export namespace MessageV2 { const Base = z.object({ id: z.string(), - sessionID: z.string(), + sessionID: SessionID.zod, }) export const User = Base.extend({ @@ -457,7 +458,7 @@ export namespace MessageV2 { Removed: BusEvent.define( "message.removed", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, messageID: z.string(), }), ), @@ -470,7 +471,7 @@ export namespace MessageV2 { PartDelta: BusEvent.define( "message.part.delta", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, messageID: z.string(), partID: z.string(), field: z.string(), @@ -480,7 +481,7 @@ export namespace MessageV2 { PartRemoved: BusEvent.define( "message.part.removed", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, messageID: z.string(), partID: z.string(), }), @@ -728,7 +729,7 @@ export namespace MessageV2 { ) } - export const stream = fn(Identifier.schema("session"), async function* (sessionID) { + export const stream = fn(SessionID.zod, async function* (sessionID) { const size = 50 let offset = 0 while (true) { @@ -792,7 +793,7 @@ export namespace MessageV2 { export const get = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, messageID: Identifier.schema("message"), }), async (input): Promise<WithParts> => { diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index 5c950d0e40..691057db20 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -1,4 +1,5 @@ import z from "zod" +import { SessionID } from "./schema" import { NamedError } from "@opencode-ai/util/error" export namespace Message { @@ -142,7 +143,7 @@ export namespace Message { error: z .discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema]) .optional(), - sessionID: z.string(), + sessionID: SessionID.zod, tool: z.record( z.string(), z diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 67edc0ecfe..33c6c9e427 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -15,6 +15,7 @@ import { Config } from "@/config/config" import { SessionCompaction } from "./compaction" import { PermissionNext } from "@/permission/next" import { Question } from "@/question" +import type { SessionID } from "./schema" export namespace SessionProcessor { const DOOM_LOOP_THRESHOLD = 3 @@ -25,7 +26,7 @@ export namespace SessionProcessor { export function create(input: { assistantMessage: MessageV2.Assistant - sessionID: string + sessionID: SessionID model: Provider.Model abort: AbortSignal }) { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 54adf1104a..5fe9121486 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -4,6 +4,7 @@ import fs from "fs/promises" import z from "zod" import { Filesystem } from "../util/filesystem" import { Identifier } from "../id/id" +import { SessionID } from "./schema" import { MessageV2 } from "./message-v2" import { Log } from "../util/log" import { SessionRevert } from "./revert" @@ -84,13 +85,13 @@ export namespace SessionPrompt { }, ) - export function assertNotBusy(sessionID: string) { + export function assertNotBusy(sessionID: SessionID) { const match = state()[sessionID] if (match) throw new Session.BusyError(sessionID) } export const PromptInput = z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, messageID: Identifier.schema("message").optional(), model: z .object({ @@ -254,7 +255,7 @@ export namespace SessionPrompt { return s[sessionID].abort.signal } - export function cancel(sessionID: string) { + export function cancel(sessionID: SessionID) { log.info("cancel", { sessionID }) const s = state() const match = s[sessionID] @@ -269,7 +270,7 @@ export namespace SessionPrompt { } export const LoopInput = z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, resume_existing: z.boolean().optional(), }) export const loop = fn(LoopInput, async (input) => { @@ -731,7 +732,7 @@ export namespace SessionPrompt { throw new Error("Impossible") }) - async function lastModel(sessionID: string) { + async function lastModel(sessionID: SessionID) { for await (const item of MessageV2.stream(sessionID)) { if (item.info.role === "user" && item.info.model) return item.info.model } @@ -1467,7 +1468,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the } export const ShellInput = z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, agent: z.string(), model: z .object({ @@ -1719,7 +1720,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the export const CommandInput = z.object({ messageID: Identifier.schema("message").optional(), - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, agent: z.string().optional(), model: z.string().optional(), arguments: z.string(), diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index ef9c7e2aac..9b42ab350a 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -1,5 +1,6 @@ import z from "zod" import { Identifier } from "../id/id" +import { SessionID } from "./schema" import { Snapshot } from "../snapshot" import { MessageV2 } from "./message-v2" import { Session } from "." @@ -15,7 +16,7 @@ export namespace SessionRevert { const log = Log.create({ service: "session.revert" }) export const RevertInput = z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, messageID: Identifier.schema("message"), partID: Identifier.schema("part").optional(), }) @@ -79,7 +80,7 @@ export namespace SessionRevert { return session } - export async function unrevert(input: { sessionID: string }) { + export async function unrevert(input: { sessionID: SessionID }) { log.info("unreverting", input) SessionPrompt.assertNotBusy(input.sessionID) const session = await Session.get(input.sessionID) diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts new file mode 100644 index 0000000000..5277f5e0b6 --- /dev/null +++ b/packages/opencode/src/session/schema.ts @@ -0,0 +1,17 @@ +import { Schema } from "effect" +import z from "zod" + +import { withStatics } from "@/util/schema" +import { Identifier } from "@/id/id" + +const sessionIdSchema = Schema.String.pipe(Schema.brand("SessionId")) + +export type SessionID = typeof sessionIdSchema.Type + +export const SessionID = sessionIdSchema.pipe( + withStatics((schema: typeof sessionIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + descending: (id?: string) => schema.makeUnsafe(Identifier.descending("session", id)), + zod: z.string().startsWith("ses").pipe(z.custom<SessionID>()), + })), +) diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 0b62dbbd8c..309b623b86 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -4,6 +4,7 @@ import type { MessageV2 } from "./message-v2" import type { Snapshot } from "../snapshot" import type { PermissionNext } from "../permission/next" import type { ProjectID } from "../project/schema" +import type { SessionID } from "./schema" import { Timestamps } from "../storage/schema.sql" type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID"> @@ -12,13 +13,13 @@ type InfoData = Omit<MessageV2.Info, "id" | "sessionID"> export const SessionTable = sqliteTable( "session", { - id: text().primaryKey(), + id: text().$type<SessionID>().primaryKey(), project_id: text() .$type<ProjectID>() .notNull() .references(() => ProjectTable.id, { onDelete: "cascade" }), workspace_id: text(), - parent_id: text(), + parent_id: text().$type<SessionID>(), slug: text().notNull(), directory: text().notNull(), title: text().notNull(), @@ -46,6 +47,7 @@ export const MessageTable = sqliteTable( { id: text().primaryKey(), session_id: text() + .$type<SessionID>() .notNull() .references(() => SessionTable.id, { onDelete: "cascade" }), ...Timestamps, @@ -61,7 +63,7 @@ export const PartTable = sqliteTable( message_id: text() .notNull() .references(() => MessageTable.id, { onDelete: "cascade" }), - session_id: text().notNull(), + session_id: text().$type<SessionID>().notNull(), ...Timestamps, data: text({ mode: "json" }).notNull().$type<PartData>(), }, @@ -72,6 +74,7 @@ export const TodoTable = sqliteTable( "todo", { session_id: text() + .$type<SessionID>() .notNull() .references(() => SessionTable.id, { onDelete: "cascade" }), content: text().notNull(), diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index 1db03b5db0..57e7939853 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -1,6 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { Instance } from "@/project/instance" +import { SessionID } from "./schema" import z from "zod" export namespace SessionStatus { @@ -28,7 +29,7 @@ export namespace SessionStatus { Status: BusEvent.define( "session.status", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, status: Info, }), ), @@ -36,7 +37,7 @@ export namespace SessionStatus { Idle: BusEvent.define( "session.idle", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, }), ), } @@ -46,7 +47,7 @@ export namespace SessionStatus { return data }) - export function get(sessionID: string) { + export function get(sessionID: SessionID) { return ( state()[sessionID] ?? { type: "idle", @@ -58,7 +59,7 @@ export namespace SessionStatus { return state() } - export function set(sessionID: string, status: Info) { + export function set(sessionID: SessionID, status: Info) { Bus.publish(Event.Status, { sessionID, status, diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 349336ba78..f489c9a628 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -4,6 +4,7 @@ import { Session } from "." import { MessageV2 } from "./message-v2" import { Identifier } from "@/id/id" +import { SessionID } from "./schema" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage/storage" @@ -68,7 +69,7 @@ export namespace SessionSummary { export const summarize = fn( z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, messageID: z.string(), }), async (input) => { @@ -80,7 +81,7 @@ export namespace SessionSummary { }, ) - async function summarizeSession(input: { sessionID: string; messages: MessageV2.WithParts[] }) { + async function summarizeSession(input: { sessionID: SessionID; messages: MessageV2.WithParts[] }) { const diffs = await computeDiff({ messages: input.messages }) await Session.setSummary({ sessionID: input.sessionID, @@ -113,7 +114,7 @@ export namespace SessionSummary { export const diff = fn( z.object({ - sessionID: Identifier.schema("session"), + sessionID: SessionID.zod, messageID: Identifier.schema("message").optional(), }), async (input) => { diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index ec2bcdda3c..02ad0d3b33 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -1,5 +1,6 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" +import { SessionID } from "./schema" import z from "zod" import { Database, eq, asc } from "../storage/db" import { TodoTable } from "./session.sql" @@ -18,13 +19,13 @@ export namespace Todo { Updated: BusEvent.define( "todo.updated", z.object({ - sessionID: z.string(), + sessionID: SessionID.zod, todos: z.array(Info), }), ), } - export function update(input: { sessionID: string; todos: Info[] }) { + export function update(input: { sessionID: SessionID; todos: Info[] }) { Database.transaction((db) => { db.delete(TodoTable).where(eq(TodoTable.session_id, input.sessionID)).run() if (input.todos.length === 0) return @@ -43,7 +44,7 @@ export namespace Todo { Bus.publish(Event.Updated, input) } - export function get(sessionID: string) { + export function get(sessionID: SessionID) { const rows = Database.use((db) => db.select().from(TodoTable).where(eq(TodoTable.session_id, sessionID)).orderBy(asc(TodoTable.position)).all(), ) diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 73fd21d905..d1b09e4bf2 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -3,6 +3,7 @@ import { Account } from "@/account" import { Config } from "@/config/config" import { Provider } from "@/provider/provider" import { Session } from "@/session" +import type { SessionID } from "@/session/schema" import { MessageV2 } from "@/session/message-v2" import { Database, eq } from "@/storage/db" import { SessionShareTable } from "./share.sql" @@ -109,7 +110,7 @@ export namespace ShareNext { }) } - export async function create(sessionID: string) { + export async function create(sessionID: SessionID) { if (disabled) return { id: "", url: "", secret: "" } log.info("creating share", { sessionID }) const req = await request() @@ -140,7 +141,7 @@ export namespace ShareNext { return result } - function get(sessionID: string) { + function get(sessionID: SessionID) { const row = Database.use((db) => db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).get(), ) @@ -186,7 +187,7 @@ export namespace ShareNext { } const queue = new Map<string, { timeout: NodeJS.Timeout; data: Map<string, Data> }>() - async function sync(sessionID: string, data: Data[]) { + async function sync(sessionID: SessionID, data: Data[]) { if (disabled) return const existing = queue.get(sessionID) if (existing) { @@ -225,7 +226,7 @@ export namespace ShareNext { queue.set(sessionID, { timeout, data: dataMap }) } - export async function remove(sessionID: string) { + export async function remove(sessionID: SessionID) { if (disabled) return log.info("removing share", { sessionID }) const share = get(sessionID) @@ -248,7 +249,7 @@ export namespace ShareNext { Database.use((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run()) } - async function fullSync(sessionID: string) { + async function fullSync(sessionID: SessionID) { log.info("full sync", { sessionID }) const session = await Session.get(sessionID) const diffs = await Session.diff(sessionID) diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index ff84dccec4..0c25cf3e95 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -7,9 +7,10 @@ import { MessageV2 } from "../session/message-v2" import { Identifier } from "../id/id" import { Provider } from "../provider/provider" import { Instance } from "../project/instance" +import type { SessionID } from "../session/schema" import EXIT_DESCRIPTION from "./plan-exit.txt" -async function getLastModel(sessionID: string) { +async function getLastModel(sessionID: SessionID) { for await (const item of MessageV2.stream(sessionID)) { if (item.info.role === "user" && item.info.model) return item.info.model } diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 8c8cf827ab..2304a2e2bf 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -2,6 +2,7 @@ import { Tool } from "./tool" import DESCRIPTION from "./task.txt" import z from "zod" import { Session } from "../session" +import { SessionID } from "../session/schema" import { MessageV2 } from "../session/message-v2" import { Identifier } from "../id/id" import { Agent } from "../agent/agent" @@ -65,7 +66,7 @@ export const TaskTool = Tool.define("task", async (ctx) => { const session = await iife(async () => { if (params.task_id) { - const found = await Session.get(params.task_id).catch(() => {}) + const found = await Session.get(SessionID.make(params.task_id)).catch(() => {}) if (found) return found } diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 0e78ba665c..7f58520c56 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -2,6 +2,7 @@ import z from "zod" import type { MessageV2 } from "../session/message-v2" import type { Agent } from "../agent/agent" import type { PermissionNext } from "../permission/next" +import type { SessionID } from "../session/schema" import { Truncate } from "./truncation" export namespace Tool { @@ -14,7 +15,7 @@ export namespace Tool { } export type Context<M extends Metadata = Metadata> = { - sessionID: string + sessionID: SessionID messageID: string agent: string abort: AbortSignal diff --git a/packages/opencode/test/cli/github-action.test.ts b/packages/opencode/test/cli/github-action.test.ts index cd64bb59ec..c2bd293d79 100644 --- a/packages/opencode/test/cli/github-action.test.ts +++ b/packages/opencode/test/cli/github-action.test.ts @@ -1,12 +1,13 @@ import { test, expect, describe } from "bun:test" import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github" import type { MessageV2 } from "../../src/session/message-v2" +import { SessionID } from "../../src/session/schema" // Helper to create minimal valid parts function createTextPart(text: string): MessageV2.Part { return { id: "1", - sessionID: "s", + sessionID: SessionID.make("s"), messageID: "m", type: "text" as const, text, @@ -16,7 +17,7 @@ function createTextPart(text: string): MessageV2.Part { function createReasoningPart(text: string): MessageV2.Part { return { id: "1", - sessionID: "s", + sessionID: SessionID.make("s"), messageID: "m", type: "reasoning" as const, text, @@ -28,7 +29,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn if (status === "completed") { return { id: "1", - sessionID: "s", + sessionID: SessionID.make("s"), messageID: "m", type: "tool" as const, callID: "c1", @@ -45,7 +46,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn } return { id: "1", - sessionID: "s", + sessionID: SessionID.make("s"), messageID: "m", type: "tool" as const, callID: "c1", @@ -61,7 +62,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn function createStepStartPart(): MessageV2.Part { return { id: "1", - sessionID: "s", + sessionID: SessionID.make("s"), messageID: "m", type: "step-start" as const, } @@ -70,7 +71,7 @@ function createStepStartPart(): MessageV2.Part { function createStepFinishPart(): MessageV2.Part { return { id: "1", - sessionID: "s", + sessionID: SessionID.make("s"), messageID: "m", type: "step-finish" as const, reason: "done", diff --git a/packages/opencode/test/memory/abort-leak.test.ts b/packages/opencode/test/memory/abort-leak.test.ts index b202c9127a..68770bf17a 100644 --- a/packages/opencode/test/memory/abort-leak.test.ts +++ b/packages/opencode/test/memory/abort-leak.test.ts @@ -2,11 +2,12 @@ import { describe, test, expect } from "bun:test" import path from "path" import { Instance } from "../../src/project/instance" import { WebFetchTool } from "../../src/tool/webfetch" +import { SessionID } from "../../src/session/schema" const projectRoot = path.join(__dirname, "../..") const ctx = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index add3332048..b66b40dc09 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -3,6 +3,7 @@ import os from "os" import { PermissionNext } from "../../src/permission/next" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" +import { SessionID } from "../../src/session/schema" // fromConfig tests @@ -462,7 +463,7 @@ test("ask - resolves immediately when action is allow", async () => { directory: tmp.path, fn: async () => { const result = await PermissionNext.ask({ - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], metadata: {}, @@ -481,7 +482,7 @@ test("ask - throws RejectedError when action is deny", async () => { fn: async () => { await expect( PermissionNext.ask({ - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["rm -rf /"], metadata: {}, @@ -499,7 +500,7 @@ test("ask - returns pending promise when action is ask", async () => { directory: tmp.path, fn: async () => { const promise = PermissionNext.ask({ - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], metadata: {}, @@ -522,7 +523,7 @@ test("reply - once resolves the pending ask", async () => { fn: async () => { const askPromise = PermissionNext.ask({ id: "permission_test1", - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], metadata: {}, @@ -547,7 +548,7 @@ test("reply - reject throws RejectedError", async () => { fn: async () => { const askPromise = PermissionNext.ask({ id: "permission_test2", - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], metadata: {}, @@ -572,7 +573,7 @@ test("reply - always persists approval and resolves", async () => { fn: async () => { const askPromise = PermissionNext.ask({ id: "permission_test3", - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], metadata: {}, @@ -594,7 +595,7 @@ test("reply - always persists approval and resolves", async () => { fn: async () => { // Stored approval should allow without asking const result = await PermissionNext.ask({ - sessionID: "session_test2", + sessionID: SessionID.make("session_test2"), permission: "bash", patterns: ["ls"], metadata: {}, @@ -613,7 +614,7 @@ test("reply - reject cancels all pending for same session", async () => { fn: async () => { const askPromise1 = PermissionNext.ask({ id: "permission_test4a", - sessionID: "session_same", + sessionID: SessionID.make("session_same"), permission: "bash", patterns: ["ls"], metadata: {}, @@ -623,7 +624,7 @@ test("reply - reject cancels all pending for same session", async () => { const askPromise2 = PermissionNext.ask({ id: "permission_test4b", - sessionID: "session_same", + sessionID: SessionID.make("session_same"), permission: "edit", patterns: ["foo.ts"], metadata: {}, @@ -655,7 +656,7 @@ test("ask - checks all patterns and stops on first deny", async () => { fn: async () => { await expect( PermissionNext.ask({ - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["echo hello", "rm -rf /"], metadata: {}, @@ -676,7 +677,7 @@ test("ask - allows all patterns when all match allow rules", async () => { directory: tmp.path, fn: async () => { const result = await PermissionNext.ask({ - sessionID: "session_test", + sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["echo hello", "ls -la", "pwd"], metadata: {}, diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index cf24faa7d2..c1c68c7472 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.test.ts @@ -2,6 +2,7 @@ import { test, expect } from "bun:test" import { Question } from "../../src/question" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" +import { SessionID } from "../../src/session/schema" test("ask - returns pending promise", async () => { await using tmp = await tmpdir({ git: true }) @@ -9,7 +10,7 @@ test("ask - returns pending promise", async () => { directory: tmp.path, fn: async () => { const promise = Question.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), questions: [ { question: "What would you like to do?", @@ -43,7 +44,7 @@ test("ask - adds to pending list", async () => { ] Question.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), questions, }) @@ -73,7 +74,7 @@ test("reply - resolves the pending ask with answers", async () => { ] const askPromise = Question.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), questions, }) @@ -97,7 +98,7 @@ test("reply - removes from pending list", async () => { directory: tmp.path, fn: async () => { Question.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), questions: [ { question: "What would you like to do?", @@ -146,7 +147,7 @@ test("reject - throws RejectedError", async () => { directory: tmp.path, fn: async () => { const askPromise = Question.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), questions: [ { question: "What would you like to do?", @@ -173,7 +174,7 @@ test("reject - removes from pending list", async () => { directory: tmp.path, fn: async () => { const askPromise = Question.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), questions: [ { question: "What would you like to do?", @@ -236,7 +237,7 @@ test("ask - handles multiple questions", async () => { ] const askPromise = Question.ask({ - sessionID: "ses_test", + sessionID: SessionID.make("ses_test"), questions, }) @@ -261,7 +262,7 @@ test("list - returns all pending requests", async () => { directory: tmp.path, fn: async () => { Question.ask({ - sessionID: "ses_test1", + sessionID: SessionID.make("ses_test1"), questions: [ { question: "Question 1?", @@ -272,7 +273,7 @@ test("list - returns all pending requests", async () => { }) Question.ask({ - sessionID: "ses_test2", + sessionID: SessionID.make("ses_test2"), questions: [ { question: "Question 2?", diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index a89a00ebc0..871fd8cb57 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -11,6 +11,7 @@ import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" import type { Agent } from "../../src/agent/agent" import type { MessageV2 } from "../../src/session/message-v2" +import { SessionID } from "../../src/session/schema" describe("session.llm.hasToolCalls", () => { test("returns false for empty messages array", () => { @@ -265,7 +266,7 @@ describe("session.llm.stream", () => { directory: tmp.path, fn: async () => { const resolved = await Provider.getModel(providerID, model.id) - const sessionID = "session-test-1" + const sessionID = SessionID.make("session-test-1") const agent = { name: "test", mode: "primary", @@ -395,7 +396,7 @@ describe("session.llm.stream", () => { directory: tmp.path, fn: async () => { const resolved = await Provider.getModel("openai", model.id) - const sessionID = "session-test-2" + const sessionID = SessionID.make("session-test-2") const agent = { name: "test", mode: "primary", @@ -517,7 +518,7 @@ describe("session.llm.stream", () => { directory: tmp.path, fn: async () => { const resolved = await Provider.getModel(providerID, model.id) - const sessionID = "session-test-3" + const sessionID = SessionID.make("session-test-3") const agent = { name: "test", mode: "primary", @@ -618,7 +619,7 @@ describe("session.llm.stream", () => { directory: tmp.path, fn: async () => { const resolved = await Provider.getModel(providerID, model.id) - const sessionID = "session-test-4" + const sessionID = SessionID.make("session-test-4") const agent = { name: "test", mode: "primary", diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 184bcd3efa..158d341f9e 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -2,8 +2,9 @@ import { describe, expect, test } from "bun:test" import { APICallError } from "ai" import { MessageV2 } from "../../src/session/message-v2" import type { Provider } from "../../src/provider/provider" +import { SessionID } from "../../src/session/schema" -const sessionID = "session" +const sessionID = SessionID.make("session") const model: Provider.Model = { id: "test-model", providerID: "test", diff --git a/packages/opencode/test/session/structured-output.test.ts b/packages/opencode/test/session/structured-output.test.ts index 2be4257dc7..537df07af6 100644 --- a/packages/opencode/test/session/structured-output.test.ts +++ b/packages/opencode/test/session/structured-output.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test" import { MessageV2 } from "../../src/session/message-v2" import { SessionPrompt } from "../../src/session/prompt" +import { SessionID } from "../../src/session/schema" describe("structured-output.OutputFormat", () => { test("parses text format", () => { @@ -96,7 +97,7 @@ describe("structured-output.UserMessage", () => { test("user message accepts outputFormat", () => { const result = MessageV2.User.safeParse({ id: "test-id", - sessionID: "test-session", + sessionID: SessionID.descending(), role: "user", time: { created: Date.now() }, agent: "default", @@ -112,7 +113,7 @@ describe("structured-output.UserMessage", () => { test("user message works without outputFormat (optional)", () => { const result = MessageV2.User.safeParse({ id: "test-id", - sessionID: "test-session", + sessionID: SessionID.descending(), role: "user", time: { created: Date.now() }, agent: "default", @@ -125,7 +126,7 @@ describe("structured-output.UserMessage", () => { describe("structured-output.AssistantMessage", () => { const baseAssistantMessage = { id: "test-id", - sessionID: "test-session", + sessionID: SessionID.descending(), role: "assistant" as const, parentID: "parent-id", modelID: "claude-3", diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts index 76dfb8b450..f5f8cbd5cd 100644 --- a/packages/opencode/test/storage/json-migration.test.ts +++ b/packages/opencode/test/storage/json-migration.test.ts @@ -11,6 +11,7 @@ import { ProjectTable } from "../../src/project/project.sql" import { ProjectID } from "../../src/project/schema" import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql" import { SessionShareTable } from "../../src/share/share.sql" +import { SessionID } from "../../src/session/schema" // Test fixtures const fixtures = { @@ -220,7 +221,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const sessions = db.select().from(SessionTable).all() expect(sessions.length).toBe(1) - expect(sessions[0].id).toBe("ses_test456def") + expect(sessions[0].id).toBe(SessionID.make("ses_test456def")) expect(sessions[0].project_id).toBe(ProjectID.make("proj_test123abc")) expect(sessions[0].slug).toBe("test-session") expect(sessions[0].title).toBe("Test Session Title") @@ -295,7 +296,7 @@ describe("JSON to SQLite migration", () => { const messages = db.select().from(MessageTable).all() expect(messages.length).toBe(1) expect(messages[0].id).toBe("msg_test789ghi") - expect(messages[0].session_id).toBe("ses_test456def") + expect(messages[0].session_id).toBe(SessionID.make("ses_test456def")) expect(messages[0].data).not.toHaveProperty("id") expect(messages[0].data).not.toHaveProperty("sessionID") @@ -303,7 +304,7 @@ describe("JSON to SQLite migration", () => { expect(parts.length).toBe(1) expect(parts[0].id).toBe("prt_testabc123") expect(parts[0].message_id).toBe("msg_test789ghi") - expect(parts[0].session_id).toBe("ses_test456def") + expect(parts[0].session_id).toBe(SessionID.make("ses_test456def")) expect(parts[0].data).not.toHaveProperty("id") expect(parts[0].data).not.toHaveProperty("messageID") expect(parts[0].data).not.toHaveProperty("sessionID") @@ -336,7 +337,7 @@ describe("JSON to SQLite migration", () => { const messages = db.select().from(MessageTable).all() expect(messages.length).toBe(1) expect(messages[0].id).toBe("msg_from_filename") // Uses filename, not JSON id - expect(messages[0].session_id).toBe("ses_test456def") + expect(messages[0].session_id).toBe(SessionID.make("ses_test456def")) }) test("uses paths for part id and messageID when JSON has different values", async () => { @@ -426,7 +427,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const sessions = db.select().from(SessionTable).all() expect(sessions.length).toBe(1) - expect(sessions[0].id).toBe("ses_migrated") + expect(sessions[0].id).toBe(SessionID.make("ses_migrated")) expect(sessions[0].project_id).toBe(ProjectID.make(gitBasedProjectID)) // Uses directory, not stale JSON }) @@ -458,7 +459,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const sessions = db.select().from(SessionTable).all() expect(sessions.length).toBe(1) - expect(sessions[0].id).toBe("ses_from_filename") // Uses filename, not JSON id + expect(sessions[0].id).toBe(SessionID.make("ses_from_filename")) // Uses filename, not JSON id expect(sessions[0].project_id).toBe(ProjectID.make("proj_test123abc")) }) diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index f81723fee0..008b4fa49c 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -4,9 +4,10 @@ import * as fs from "fs/promises" import { ApplyPatchTool } from "../../src/tool/apply_patch" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" +import { SessionID } from "../../src/session/schema" const baseCtx = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index ac93016927..d4a6c7ccdb 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -7,9 +7,10 @@ import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" import type { PermissionNext } from "../../src/permission/next" import { Truncate } from "../../src/tool/truncation" +import { SessionID } from "../../src/session/schema" const ctx = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index bcf75da05c..601995889e 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -5,9 +5,10 @@ import { EditTool } from "../../src/tool/edit" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import { FileTime } from "../../src/file/time" +import { SessionID } from "../../src/session/schema" const ctx = { - sessionID: "test-edit-session", + sessionID: SessionID.make("ses_test-edit-session"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index a75f767b3b..fe33d69151 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -4,9 +4,10 @@ import type { Tool } from "../../src/tool/tool" import { Instance } from "../../src/project/instance" import { assertExternalDirectory } from "../../src/tool/external-directory" import type { PermissionNext } from "../../src/permission/next" +import { SessionID } from "../../src/session/schema" const baseCtx: Omit<Tool.Context, "ask"> = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index e774580df6..5823553722 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -3,9 +3,10 @@ import path from "path" import { GrepTool } from "../../src/tool/grep" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" +import { SessionID } from "../../src/session/schema" const ctx = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index 4a436186db..69e211abdd 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -2,9 +2,10 @@ import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test" import { z } from "zod" import { QuestionTool } from "../../src/tool/question" import * as QuestionModule from "../../src/question" +import { SessionID } from "../../src/session/schema" const ctx = { - sessionID: "test-session", + sessionID: SessionID.make("ses_test-session"), messageID: "test-message", callID: "test-call", agent: "test-agent", diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index b22fc3e712..45f3c1b32e 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -6,11 +6,12 @@ import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" import { PermissionNext } from "../../src/permission/next" import { Agent } from "../../src/agent/agent" +import { SessionID } from "../../src/session/schema" const FIXTURES_DIR = path.join(import.meta.dir, "fixtures") const ctx = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index c47259f155..fa67c293e9 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -6,9 +6,10 @@ import type { Tool } from "../../src/tool/tool" import { Instance } from "../../src/project/instance" import { SkillTool } from "../../src/tool/skill" import { tmpdir } from "../fixture/fixture" +import { SessionID } from "../../src/session/schema" const baseCtx: Omit<Tool.Context, "ask"> = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index 0214700fed..769a61e165 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -2,11 +2,12 @@ import { describe, expect, test } from "bun:test" import path from "path" import { Instance } from "../../src/project/instance" import { WebFetchTool } from "../../src/tool/webfetch" +import { SessionID } from "../../src/session/schema" const projectRoot = path.join(import.meta.dir, "../..") const ctx = { - sessionID: "test", + sessionID: SessionID.make("ses_test"), messageID: "message", callID: "", agent: "build", diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 695d48ccbb..62279057a9 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -4,9 +4,10 @@ import fs from "fs/promises" import { WriteTool } from "../../src/tool/write" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" +import { SessionID } from "../../src/session/schema" const ctx = { - sessionID: "test-write-session", + sessionID: SessionID.make("ses_test-write-session"), messageID: "", callID: "", agent: "build", From 34fa5de9c5753bfa32c86756105b1de6588b3111 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:17:42 +0000 Subject: [PATCH 109/200] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 36 ----- packages/sdk/openapi.json | 172 ++++++++++++++---------- 2 files changed, 102 insertions(+), 106 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a47b18db21..8d6b04503b 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2970,9 +2970,6 @@ export type SessionChildrenResponse = SessionChildrenResponses[keyof SessionChil export type SessionTodoData = { body?: never path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3011,9 +3008,6 @@ export type SessionInitData = { messageID: string } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3199,9 +3193,6 @@ export type SessionSummarizeData = { auto?: boolean } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3236,9 +3227,6 @@ export type SessionSummarizeResponse = SessionSummarizeResponses[keyof SessionSu export type SessionMessagesData = { body?: never path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3295,9 +3283,6 @@ export type SessionPromptData = { parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput> } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3335,9 +3320,6 @@ export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptRe export type SessionDeleteMessageData = { body?: never path: { - /** - * Session ID - */ sessionID: string /** * Message ID @@ -3376,9 +3358,6 @@ export type SessionDeleteMessageResponse = SessionDeleteMessageResponses[keyof S export type SessionMessageData = { body?: never path: { - /** - * Session ID - */ sessionID: string /** * Message ID @@ -3420,9 +3399,6 @@ export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessag export type PartDeleteData = { body?: never path: { - /** - * Session ID - */ sessionID: string /** * Message ID @@ -3465,9 +3441,6 @@ export type PartDeleteResponse = PartDeleteResponses[keyof PartDeleteResponses] export type PartUpdateData = { body?: Part path: { - /** - * Session ID - */ sessionID: string /** * Message ID @@ -3528,9 +3501,6 @@ export type SessionPromptAsyncData = { parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput> } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3580,9 +3550,6 @@ export type SessionCommandData = { }> } path: { - /** - * Session ID - */ sessionID: string } query?: { @@ -3627,9 +3594,6 @@ export type SessionShellData = { command: string } path: { - /** - * Session ID - */ sessionID: string } query?: { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index c3e54a7a11..7b0ad181a3 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -2066,7 +2066,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -2231,10 +2232,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Get session todos", @@ -2304,10 +2305,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Initialize session", @@ -2461,7 +2462,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -2530,7 +2532,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -2728,10 +2731,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Summarize session", @@ -2820,10 +2823,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "query", @@ -2910,10 +2913,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Send message", @@ -3064,10 +3067,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", @@ -3153,10 +3156,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", @@ -3232,10 +3235,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", @@ -3317,10 +3320,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true }, { "in": "path", @@ -3413,10 +3416,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Send async message", @@ -3548,10 +3551,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Send command", @@ -3693,10 +3696,10 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, - "required": true, - "description": "Session ID" + "required": true } ], "summary": "Run shell command", @@ -3793,7 +3796,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -3882,7 +3886,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true } @@ -3951,7 +3956,8 @@ "in": "path", "name": "sessionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "required": true }, @@ -6306,7 +6312,7 @@ "sessionID": { "description": "Session ID to navigate to", "type": "string", - "pattern": "^ses" + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -7198,7 +7204,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "role": { "type": "string", @@ -7438,7 +7445,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "role": { "type": "string", @@ -7607,7 +7615,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -7625,7 +7634,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -7672,7 +7682,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -7715,7 +7726,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -7880,7 +7892,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8073,7 +8086,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8108,7 +8122,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8130,7 +8145,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8188,7 +8204,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8210,7 +8227,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8238,7 +8256,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8279,7 +8298,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8313,7 +8333,8 @@ "type": "string" }, "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8401,7 +8422,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8432,7 +8454,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "messageID": { "type": "string" @@ -8518,7 +8541,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "requestID": { "type": "string" @@ -8587,7 +8611,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "status": { "$ref": "#/components/schemas/SessionStatus" @@ -8609,7 +8634,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -8723,7 +8749,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "requestID": { "type": "string" @@ -8751,7 +8778,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "requestID": { "type": "string" @@ -8773,7 +8801,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -8845,7 +8874,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "todos": { "type": "array", @@ -8966,7 +8996,7 @@ "sessionID": { "description": "Session ID to navigate to", "type": "string", - "pattern": "^ses" + "pattern": "^ses.*" } }, "required": ["sessionID"] @@ -9239,7 +9269,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "diff": { "type": "array", @@ -9264,7 +9295,8 @@ "type": "object", "properties": { "sessionID": { - "type": "string" + "type": "string", + "pattern": "^ses.*" }, "error": { "anyOf": [ From f1c3a4419060c9ddecd70e5599578defaa73de00 Mon Sep 17 00:00:00 2001 From: John Mylchreest <jmylchreest@gmail.com> Date: Wed, 11 Mar 2026 23:26:54 +0000 Subject: [PATCH 110/200] fix: resolve symlinks in Instance cache to prevent duplicate contexts (#16651) Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com> --- packages/opencode/src/util/filesystem.ts | 10 +++- .../opencode/test/util/filesystem.test.ts | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/util/filesystem.ts b/packages/opencode/src/util/filesystem.ts index fb1f5ab9e5..37f00c6b9c 100644 --- a/packages/opencode/src/util/filesystem.ts +++ b/packages/opencode/src/util/filesystem.ts @@ -114,8 +114,16 @@ export namespace Filesystem { } // We cannot rely on path.resolve() here because git.exe may come from Git Bash, Cygwin, or MSYS2, so we need to translate these paths at the boundary. + // Also resolves symlinks so that callers using the result as a cache key + // always get the same canonical path for a given physical directory. export function resolve(p: string): string { - return normalizePath(pathResolve(windowsPath(p))) + const resolved = pathResolve(windowsPath(p)) + try { + return normalizePath(realpathSync(resolved)) + } catch (e) { + if (isEnoent(e)) return normalizePath(resolved) + throw e + } } export function windowsPath(p: string): string { diff --git a/packages/opencode/test/util/filesystem.test.ts b/packages/opencode/test/util/filesystem.test.ts index c757e3424d..aea0b1db87 100644 --- a/packages/opencode/test/util/filesystem.test.ts +++ b/packages/opencode/test/util/filesystem.test.ts @@ -502,5 +502,57 @@ describe("filesystem", () => { const drive = tmp.path[0].toLowerCase() expect(Filesystem.resolve(`/mnt/${drive}`)).toBe(Filesystem.resolve(`${drive.toUpperCase()}:/`)) }) + + test("resolves symlinked directory to canonical path", async () => { + await using tmp = await tmpdir() + const target = path.join(tmp.path, "real") + await fs.mkdir(target) + const link = path.join(tmp.path, "link") + await fs.symlink(target, link) + expect(Filesystem.resolve(link)).toBe(Filesystem.resolve(target)) + }) + + test("returns unresolved path when target does not exist", async () => { + await using tmp = await tmpdir() + const missing = path.join(tmp.path, "does-not-exist-" + Date.now()) + const result = Filesystem.resolve(missing) + expect(result).toBe(Filesystem.normalizePath(path.resolve(missing))) + }) + + test("throws ELOOP on symlink cycle", async () => { + await using tmp = await tmpdir() + const a = path.join(tmp.path, "a") + const b = path.join(tmp.path, "b") + await fs.symlink(b, a) + await fs.symlink(a, b) + expect(() => Filesystem.resolve(a)).toThrow() + }) + + // Windows: chmod(0o000) is a no-op, so EACCES cannot be triggered + test("throws EACCES on permission-denied symlink target", async () => { + if (process.platform === "win32") return + if (process.getuid?.() === 0) return // skip when running as root + await using tmp = await tmpdir() + const dir = path.join(tmp.path, "restricted") + await fs.mkdir(dir) + const link = path.join(tmp.path, "link") + await fs.symlink(dir, link) + await fs.chmod(dir, 0o000) + try { + expect(() => Filesystem.resolve(path.join(link, "child"))).toThrow() + } finally { + await fs.chmod(dir, 0o755) + } + }) + + // Windows: traversing through a file throws ENOENT (not ENOTDIR), + // which resolve() catches as a fallback instead of rethrowing + test("rethrows non-ENOENT errors", async () => { + if (process.platform === "win32") return + await using tmp = await tmpdir() + const file = path.join(tmp.path, "not-a-directory") + await fs.writeFile(file, "x") + expect(() => Filesystem.resolve(path.join(file, "child"))).toThrow() + }) }) }) From 16a6d6febad7b884fe44ac52a50eeeaea2619018 Mon Sep 17 00:00:00 2001 From: Kit Langton <kit.langton@gmail.com> Date: Wed, 11 Mar 2026 19:30:17 -0400 Subject: [PATCH 111/200] feat(id): brand WorkspaceID through Drizzle and Zod schemas (#16964) --- packages/opencode/script/seed-e2e.ts | 3 ++- packages/opencode/src/cli/cmd/debug/agent.ts | 3 ++- packages/opencode/src/cli/cmd/github.ts | 5 ++-- packages/opencode/src/cli/cmd/import.ts | 17 ++++++++---- .../cli/cmd/tui/component/prompt/index.tsx | 3 ++- packages/opencode/src/command/index.ts | 4 +-- packages/opencode/src/control-plane/schema.ts | 17 ++++++++++++ packages/opencode/src/control-plane/types.ts | 4 +-- .../src/control-plane/workspace-context.ts | 5 ++-- .../control-plane/workspace-server/server.ts | 7 ++--- .../src/control-plane/workspace.sql.ts | 3 ++- .../opencode/src/control-plane/workspace.ts | 10 +++---- packages/opencode/src/permission/index.ts | 4 +-- packages/opencode/src/permission/next.ts | 4 +-- packages/opencode/src/question/index.ts | 6 ++--- .../opencode/src/server/routes/session.ts | 14 +++++----- packages/opencode/src/server/server.ts | 5 ++-- packages/opencode/src/session/compaction.ts | 12 ++++----- packages/opencode/src/session/index.ts | 27 ++++++++++--------- packages/opencode/src/session/message-v2.ts | 21 +++++++-------- packages/opencode/src/session/processor.ts | 2 +- packages/opencode/src/session/prompt.ts | 18 ++++++------- packages/opencode/src/session/revert.ts | 4 +-- packages/opencode/src/session/schema.ts | 12 +++++++++ packages/opencode/src/session/session.sql.ts | 10 ++++--- packages/opencode/src/session/summary.ts | 6 ++--- packages/opencode/src/tool/plan.ts | 6 ++--- packages/opencode/src/tool/task.ts | 4 +-- packages/opencode/src/tool/tool.ts | 4 +-- .../opencode/test/cli/github-action.test.ts | 14 +++++----- .../session-proxy-middleware.test.ts | 6 ++--- .../test/control-plane/workspace-sync.test.ts | 6 ++--- .../opencode/test/memory/abort-leak.test.ts | 4 +-- packages/opencode/test/session/llm.test.ts | 10 +++---- .../opencode/test/session/message-v2.test.ts | 4 +-- .../test/session/revert-compact.test.ts | 13 ++++----- .../opencode/test/session/session.test.ts | 3 ++- .../test/session/structured-output.test.ts | 10 +++---- .../test/storage/json-migration.test.ts | 12 ++++----- .../opencode/test/tool/apply_patch.test.ts | 4 +-- packages/opencode/test/tool/bash.test.ts | 4 +-- packages/opencode/test/tool/edit.test.ts | 4 +-- .../test/tool/external-directory.test.ts | 4 +-- packages/opencode/test/tool/grep.test.ts | 4 +-- packages/opencode/test/tool/question.test.ts | 4 +-- packages/opencode/test/tool/read.test.ts | 4 +-- packages/opencode/test/tool/skill.test.ts | 4 +-- packages/opencode/test/tool/webfetch.test.ts | 4 +-- packages/opencode/test/tool/write.test.ts | 4 +-- 49 files changed, 205 insertions(+), 157 deletions(-) create mode 100644 packages/opencode/src/control-plane/schema.ts diff --git a/packages/opencode/script/seed-e2e.ts b/packages/opencode/script/seed-e2e.ts index ba2155cb69..d233341448 100644 --- a/packages/opencode/script/seed-e2e.ts +++ b/packages/opencode/script/seed-e2e.ts @@ -12,6 +12,7 @@ const seed = async () => { const { InstanceBootstrap } = await import("../src/project/bootstrap") const { Session } = await import("../src/session") const { Identifier } = await import("../src/id/id") + const { MessageID } = await import("../src/session/schema") const { Project } = await import("../src/project/project") await Instance.provide({ @@ -19,7 +20,7 @@ const seed = async () => { init: InstanceBootstrap, fn: async () => { const session = await Session.create({ title }) - const messageID = Identifier.descending("message") + const messageID = MessageID.ascending() const partID = Identifier.descending("part") const message = { id: messageID, diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index fe30034859..47e1d36373 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/packages/opencode/src/cli/cmd/debug/agent.ts @@ -5,6 +5,7 @@ import { Provider } from "../../../provider/provider" import { Session } from "../../../session" import type { MessageV2 } from "../../../session/message-v2" import { Identifier } from "../../../id/id" +import { MessageID } from "../../../session/schema" import { ToolRegistry } from "../../../tool/registry" import { Instance } from "../../../project/instance" import { PermissionNext } from "../../../permission/next" @@ -113,7 +114,7 @@ function parseToolParams(input?: string) { async function createToolContext(agent: Agent.Info) { const session = await Session.create({ title: `Debug tool run (${agent.name})` }) - const messageID = Identifier.ascending("message") + const messageID = MessageID.ascending() const model = agent.model ?? (await Provider.defaultModel()) const now = Date.now() const message: MessageV2.Assistant = { diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index ad893ce4f3..af9ff4a428 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -24,6 +24,7 @@ import { bootstrap } from "../bootstrap" import { Session } from "../../session" import type { SessionID } from "../../session/schema" import { Identifier } from "../../id/id" +import { MessageID } from "../../session/schema" import { Provider } from "../../provider/provider" import { Bus } from "../../bus" import { MessageV2 } from "../../session/message-v2" @@ -935,7 +936,7 @@ export const GithubRunCommand = cmd({ const result = await SessionPrompt.prompt({ sessionID: session.id, - messageID: Identifier.ascending("message"), + messageID: MessageID.ascending(), variant, model: { providerID, @@ -989,7 +990,7 @@ export const GithubRunCommand = cmd({ console.log("Requesting summary from agent...") const summary = await SessionPrompt.prompt({ sessionID: session.id, - messageID: Identifier.ascending("message"), + messageID: MessageID.ascending(), variant, model: { providerID, diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index f06b8f39fe..a5a21d1c3d 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -1,7 +1,8 @@ import type { Argv } from "yargs" import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2" import { Session } from "../../session" -import { SessionID } from "../../session/schema" +import { SessionID, MessageID } from "../../session/schema" +import { WorkspaceID } from "../../control-plane/schema" import { cmd } from "./cmd" import { bootstrap } from "../bootstrap" import { Database } from "../../storage/db" @@ -157,7 +158,11 @@ export const ImportCommand = cmd({ ...exportData.info, id: SessionID.make(exportData.info.id), parentID: exportData.info.parentID ? SessionID.make(exportData.info.parentID) : undefined, + workspaceID: exportData.info.workspaceID ? WorkspaceID.make(exportData.info.workspaceID) : undefined, projectID: Instance.project.id, + revert: exportData.info.revert + ? { ...exportData.info.revert, messageID: MessageID.make(exportData.info.revert.messageID) } + : undefined, }) Database.use((db) => db @@ -168,28 +173,30 @@ export const ImportCommand = cmd({ ) for (const msg of exportData.messages) { + const { id: _mid, sessionID: _msid, ...msgData } = msg.info Database.use((db) => db .insert(MessageTable) .values({ - id: msg.info.id, + id: MessageID.make(msg.info.id), session_id: row.id, time_created: msg.info.time?.created ?? Date.now(), - data: msg.info, + data: msgData, }) .onConflictDoNothing() .run(), ) for (const part of msg.parts) { + const { id: _pid, sessionID: _psid, messageID: _pmid, ...partData } = part Database.use((db) => db .insert(PartTable) .values({ id: part.id, - message_id: msg.info.id, + message_id: MessageID.make(msg.info.id), session_id: row.id, - data: part, + data: partData, }) .onConflictDoNothing() .run(), diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 2d99051fb9..9923cd3fab 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -10,6 +10,7 @@ import { useSDK } from "@tui/context/sdk" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" import { Identifier } from "@/id/id" +import { MessageID } from "@/session/schema" import { createStore, produce } from "solid-js/store" import { useKeybind } from "@tui/context/keybind" import { usePromptHistory, type PromptInfo } from "./history" @@ -561,7 +562,7 @@ export function Prompt(props: PromptProps) { sessionID = res.data.id } - const messageID = Identifier.ascending("message") + const messageID = MessageID.ascending() let inputText = store.prompt.input // Expand pasted text inline before submitting diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 3f9f25e1dc..2c47984fdd 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -1,5 +1,5 @@ import { BusEvent } from "@/bus/bus-event" -import { SessionID } from "@/session/schema" +import { SessionID, MessageID } from "@/session/schema" import z from "zod" import { Config } from "../config/config" import { Instance } from "../project/instance" @@ -17,7 +17,7 @@ export namespace Command { name: z.string(), sessionID: SessionID.zod, arguments: z.string(), - messageID: Identifier.schema("message"), + messageID: MessageID.zod, }), ), } diff --git a/packages/opencode/src/control-plane/schema.ts b/packages/opencode/src/control-plane/schema.ts new file mode 100644 index 0000000000..00a9f8b56a --- /dev/null +++ b/packages/opencode/src/control-plane/schema.ts @@ -0,0 +1,17 @@ +import { Schema } from "effect" +import z from "zod" + +import { withStatics } from "@/util/schema" +import { Identifier } from "@/id/id" + +const workspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceId")) + +export type WorkspaceID = typeof workspaceIdSchema.Type + +export const WorkspaceID = workspaceIdSchema.pipe( + withStatics((schema: typeof workspaceIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("workspace", id)), + zod: z.string().startsWith("wrk").pipe(z.custom<WorkspaceID>()), + })), +) diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts index 53a6c8a073..ab628a6938 100644 --- a/packages/opencode/src/control-plane/types.ts +++ b/packages/opencode/src/control-plane/types.ts @@ -1,9 +1,9 @@ import z from "zod" -import { Identifier } from "@/id/id" import { ProjectID } from "@/project/schema" +import { WorkspaceID } from "./schema" export const WorkspaceInfo = z.object({ - id: Identifier.schema("workspace"), + id: WorkspaceID.zod, type: z.string(), branch: z.string().nullable(), name: z.string().nullable(), diff --git a/packages/opencode/src/control-plane/workspace-context.ts b/packages/opencode/src/control-plane/workspace-context.ts index f7297b3f4b..cdd975dc4f 100644 --- a/packages/opencode/src/control-plane/workspace-context.ts +++ b/packages/opencode/src/control-plane/workspace-context.ts @@ -1,13 +1,14 @@ import { Context } from "../util/context" +import type { WorkspaceID } from "./schema" interface Context { - workspaceID?: string + workspaceID?: WorkspaceID } const context = Context.create<Context>("workspace") export const WorkspaceContext = { - async provide<R>(input: { workspaceID?: string; fn: () => R }): Promise<R> { + async provide<R>(input: { workspaceID?: WorkspaceID; fn: () => R }): Promise<R> { return context.provide({ workspaceID: input.workspaceID }, async () => { return input.fn() }) diff --git a/packages/opencode/src/control-plane/workspace-server/server.ts b/packages/opencode/src/control-plane/workspace-server/server.ts index fd7fd93086..b0744fe025 100644 --- a/packages/opencode/src/control-plane/workspace-server/server.ts +++ b/packages/opencode/src/control-plane/workspace-server/server.ts @@ -4,6 +4,7 @@ import { InstanceBootstrap } from "../../project/bootstrap" import { SessionRoutes } from "../../server/routes/session" import { WorkspaceServerRoutes } from "./routes" import { WorkspaceContext } from "../workspace-context" +import { WorkspaceID } from "../schema" export namespace WorkspaceServer { export function App() { @@ -20,9 +21,9 @@ export namespace WorkspaceServer { return new Hono() .use(async (c, next) => { - const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") + const rawWorkspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") const raw = c.req.query("directory") || c.req.header("x-opencode-directory") - if (workspaceID == null) { + if (rawWorkspaceID == null) { throw new Error("workspaceID parameter is required") } if (raw == null) { @@ -38,7 +39,7 @@ export namespace WorkspaceServer { })() return WorkspaceContext.provide({ - workspaceID, + workspaceID: WorkspaceID.make(rawWorkspaceID), async fn() { return Instance.provide({ directory, diff --git a/packages/opencode/src/control-plane/workspace.sql.ts b/packages/opencode/src/control-plane/workspace.sql.ts index eb7d21d1e8..272907da15 100644 --- a/packages/opencode/src/control-plane/workspace.sql.ts +++ b/packages/opencode/src/control-plane/workspace.sql.ts @@ -1,9 +1,10 @@ import { sqliteTable, text } from "drizzle-orm/sqlite-core" import { ProjectTable } from "../project/project.sql" import type { ProjectID } from "../project/schema" +import type { WorkspaceID } from "./schema" export const WorkspaceTable = sqliteTable("workspace", { - id: text().primaryKey(), + id: text().$type<WorkspaceID>().primaryKey(), type: text().notNull(), branch: text(), name: text(), diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index f3af985cac..c3c28ed605 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,5 +1,4 @@ import z from "zod" -import { Identifier } from "@/id/id" import { fn } from "@/util/fn" import { Database, eq } from "@/storage/db" import { Project } from "@/project/project" @@ -10,6 +9,7 @@ import { ProjectID } from "@/project/schema" import { WorkspaceTable } from "./workspace.sql" import { getAdaptor } from "./adaptors" import { WorkspaceInfo } from "./types" +import { WorkspaceID } from "./schema" import { parseSSE } from "./sse" export namespace Workspace { @@ -46,7 +46,7 @@ export namespace Workspace { } const CreateInput = z.object({ - id: Identifier.schema("workspace").optional(), + id: WorkspaceID.zod.optional(), type: Info.shape.type, branch: Info.shape.branch, projectID: ProjectID.zod, @@ -54,7 +54,7 @@ export namespace Workspace { }) export const create = fn(CreateInput, async (input) => { - const id = Identifier.ascending("workspace", input.id) + const id = WorkspaceID.ascending(input.id) const adaptor = await getAdaptor(input.type) const config = await adaptor.configure({ ...input, id, name: null, directory: null }) @@ -94,13 +94,13 @@ export namespace Workspace { return rows.map(fromRow).sort((a, b) => a.id.localeCompare(b.id)) } - export const get = fn(Identifier.schema("workspace"), async (id) => { + export const get = fn(WorkspaceID.zod, async (id) => { const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get()) if (!row) return return fromRow(row) }) - export const remove = fn(Identifier.schema("workspace"), async (id) => { + export const remove = fn(WorkspaceID.zod, async (id) => { const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get()) if (row) { const info = fromRow(row) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 16a94ec54d..8a2ba64e11 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -1,6 +1,6 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" -import { SessionID } from "@/session/schema" +import { SessionID, MessageID } from "@/session/schema" import z from "zod" import { Log } from "../util/log" import { Identifier } from "../id/id" @@ -26,7 +26,7 @@ export namespace Permission { type: z.string(), pattern: z.union([z.string(), z.array(z.string())]).optional(), sessionID: SessionID.zod, - messageID: z.string(), + messageID: MessageID.zod, callID: z.string().optional(), message: z.string(), metadata: z.record(z.string(), z.any()), diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 6bfe0035ba..2d665a832a 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -2,7 +2,7 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { Config } from "@/config/config" import { Identifier } from "@/id/id" -import { SessionID } from "@/session/schema" +import { SessionID, MessageID } from "@/session/schema" import { Instance } from "@/project/instance" import { Database, eq } from "@/storage/db" import { PermissionTable } from "@/session/session.sql" @@ -77,7 +77,7 @@ export namespace PermissionNext { always: z.string().array(), tool: z .object({ - messageID: z.string(), + messageID: MessageID.zod, callID: z.string(), }) .optional(), diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index ba1fc66506..2142fe183f 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -1,7 +1,7 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { Identifier } from "@/id/id" -import { SessionID } from "@/session/schema" +import { SessionID, MessageID } from "@/session/schema" import { Instance } from "@/project/instance" import { Log } from "@/util/log" import z from "zod" @@ -39,7 +39,7 @@ export namespace Question { questions: z.array(Info).describe("Questions to ask"), tool: z .object({ - messageID: z.string(), + messageID: MessageID.zod, callID: z.string(), }) .optional(), @@ -98,7 +98,7 @@ export namespace Question { export async function ask(input: { sessionID: SessionID questions: Info[] - tool?: { messageID: string; callID: string } + tool?: { messageID: MessageID; callID: string } }): Promise<Answer[]> { const s = await state() const id = Identifier.ascending("question") diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 8e465d7ec2..26468c52c5 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -1,7 +1,7 @@ import { Hono } from "hono" import { stream } from "hono/streaming" import { describeRoute, validator, resolver } from "hono-openapi" -import { SessionID } from "@/session/schema" +import { SessionID, MessageID } from "@/session/schema" import z from "zod" import { Session } from "../../session" import { MessageV2 } from "../../session/message-v2" @@ -607,7 +607,7 @@ export const SessionRoutes = lazy(() => "param", z.object({ sessionID: SessionID.zod, - messageID: z.string().meta({ description: "Message ID" }), + messageID: MessageID.zod, }), ), async (c) => { @@ -642,7 +642,7 @@ export const SessionRoutes = lazy(() => "param", z.object({ sessionID: SessionID.zod, - messageID: z.string().meta({ description: "Message ID" }), + messageID: MessageID.zod, }), ), async (c) => { @@ -676,8 +676,8 @@ export const SessionRoutes = lazy(() => "param", z.object({ sessionID: SessionID.zod, - messageID: z.string().meta({ description: "Message ID" }), - partID: z.string().meta({ description: "Part ID" }), + messageID: MessageID.zod, + partID: z.string(), }), ), async (c) => { @@ -711,8 +711,8 @@ export const SessionRoutes = lazy(() => "param", z.object({ sessionID: SessionID.zod, - messageID: z.string().meta({ description: "Message ID" }), - partID: z.string().meta({ description: "Part ID" }), + messageID: MessageID.zod, + partID: z.string(), }), ), validator("json", MessageV2.Part), diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 0566547b73..14c2346a61 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -22,6 +22,7 @@ import { Flag } from "../flag/flag" import { Command } from "../command" import { Global } from "../global" import { WorkspaceContext } from "../control-plane/workspace-context" +import { WorkspaceID } from "../control-plane/schema" import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware" import { ProjectRoutes } from "./routes/project" import { SessionRoutes } from "./routes/session" @@ -190,7 +191,7 @@ export namespace Server { ) .use(async (c, next) => { if (c.req.path === "/log") return next() - const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") + const rawWorkspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace") const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() const directory = Filesystem.resolve( (() => { @@ -203,7 +204,7 @@ export namespace Server { ) return WorkspaceContext.provide({ - workspaceID, + workspaceID: rawWorkspaceID ? WorkspaceID.make(rawWorkspaceID) : undefined, async fn() { return Instance.provide({ directory, diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index d26c3bb6b9..37a7cad84d 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -2,7 +2,7 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { Session } from "." import { Identifier } from "../id/id" -import { SessionID } from "./schema" +import { SessionID, MessageID } from "./schema" import { Instance } from "../project/instance" import { Provider } from "../provider/provider" import { MessageV2 } from "./message-v2" @@ -100,7 +100,7 @@ export namespace SessionCompaction { } export async function process(input: { - parentID: string + parentID: MessageID messages: MessageV2.WithParts[] sessionID: SessionID abort: AbortSignal @@ -134,7 +134,7 @@ export namespace SessionCompaction { ? await Provider.getModel(agent.model.providerID, agent.model.modelID) : await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID) const msg = (await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "assistant", parentID: input.parentID, sessionID: input.sessionID, @@ -237,7 +237,7 @@ When constructing the summary, try to stick to this template: if (replay) { const original = replay.info as MessageV2.User const replayMsg = await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "user", sessionID: input.sessionID, time: { created: Date.now() }, @@ -263,7 +263,7 @@ When constructing the summary, try to stick to this template: } } else { const continueMsg = await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "user", sessionID: input.sessionID, time: { created: Date.now() }, @@ -307,7 +307,7 @@ When constructing the summary, try to stick to this template: }), async (input) => { const msg = await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "user", model: input.model, sessionID: input.sessionID, diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 76b550d309..63495cd00c 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -24,7 +24,8 @@ import { Command } from "../command" import { Snapshot } from "@/snapshot" import { WorkspaceContext } from "../control-plane/workspace-context" import { ProjectID } from "../project/schema" -import { SessionID } from "./schema" +import { WorkspaceID } from "../control-plane/schema" +import { SessionID, MessageID } from "./schema" import type { Provider } from "@/provider/provider" import { PermissionNext } from "@/permission/next" @@ -123,7 +124,7 @@ export namespace Session { id: SessionID.zod, slug: z.string(), projectID: ProjectID.zod, - workspaceID: z.string().optional(), + workspaceID: WorkspaceID.zod.optional(), directory: z.string(), parentID: SessionID.zod.optional(), summary: z @@ -150,7 +151,7 @@ export namespace Session { permission: PermissionNext.Ruleset.optional(), revert: z .object({ - messageID: z.string(), + messageID: MessageID.zod, partID: z.string().optional(), snapshot: z.string().optional(), diff: z.string().optional(), @@ -221,7 +222,7 @@ export namespace Session { parentID: SessionID.zod.optional(), title: z.string().optional(), permission: Info.shape.permission, - workspaceID: Identifier.schema("workspace").optional(), + workspaceID: WorkspaceID.zod.optional(), }) .optional(), async (input) => { @@ -238,7 +239,7 @@ export namespace Session { export const fork = fn( z.object({ sessionID: SessionID.zod, - messageID: Identifier.schema("message").optional(), + messageID: MessageID.zod.optional(), }), async (input) => { const original = await get(input.sessionID) @@ -250,11 +251,11 @@ export namespace Session { title, }) const msgs = await messages({ sessionID: input.sessionID }) - const idMap = new Map<string, string>() + const idMap = new Map<string, MessageID>() for (const msg of msgs) { if (input.messageID && msg.info.id >= input.messageID) break - const newID = Identifier.ascending("message") + const newID = MessageID.ascending() idMap.set(msg.info.id, newID) const parentID = msg.info.role === "assistant" && msg.info.parentID ? idMap.get(msg.info.parentID) : undefined @@ -297,7 +298,7 @@ export namespace Session { id?: SessionID title?: string parentID?: SessionID - workspaceID?: string + workspaceID?: WorkspaceID directory: string permission?: PermissionNext.Ruleset }) { @@ -538,7 +539,7 @@ export namespace Session { export function* list(input?: { directory?: string - workspaceID?: string + workspaceID?: WorkspaceID roots?: boolean start?: number search?: string @@ -707,7 +708,7 @@ export namespace Session { export const removeMessage = fn( z.object({ sessionID: SessionID.zod, - messageID: Identifier.schema("message"), + messageID: MessageID.zod, }), async (input) => { // CASCADE delete handles parts automatically @@ -729,7 +730,7 @@ export namespace Session { export const removePart = fn( z.object({ sessionID: SessionID.zod, - messageID: Identifier.schema("message"), + messageID: MessageID.zod, partID: Identifier.schema("part"), }), async (input) => { @@ -777,7 +778,7 @@ export namespace Session { export const updatePartDelta = fn( z.object({ sessionID: SessionID.zod, - messageID: z.string(), + messageID: MessageID.zod, partID: z.string(), field: z.string(), delta: z.string(), @@ -877,7 +878,7 @@ export namespace Session { sessionID: SessionID.zod, modelID: z.string(), providerID: z.string(), - messageID: Identifier.schema("message"), + messageID: MessageID.zod, }), async (input) => { await SessionPrompt.command({ diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 43d735e4bd..0fa842b299 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,9 +1,8 @@ import { BusEvent } from "@/bus/bus-event" -import { SessionID } from "./schema" +import { SessionID, MessageID } from "./schema" import z from "zod" import { NamedError } from "@opencode-ai/util/error" import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" -import { Identifier } from "../id/id" import { LSP } from "../lsp" import { Snapshot } from "@/snapshot" import { fn } from "@/util/fn" @@ -81,7 +80,7 @@ export namespace MessageV2 { const PartBase = z.object({ id: z.string(), sessionID: SessionID.zod, - messageID: z.string(), + messageID: MessageID.zod, }) export const SnapshotPart = PartBase.extend({ @@ -344,7 +343,7 @@ export namespace MessageV2 { export type ToolPart = z.infer<typeof ToolPart> const Base = z.object({ - id: z.string(), + id: MessageID.zod, sessionID: SessionID.zod, }) @@ -411,7 +410,7 @@ export namespace MessageV2 { APIError.Schema, ]) .optional(), - parentID: z.string(), + parentID: MessageID.zod, modelID: z.string(), providerID: z.string(), /** @@ -459,7 +458,7 @@ export namespace MessageV2 { "message.removed", z.object({ sessionID: SessionID.zod, - messageID: z.string(), + messageID: MessageID.zod, }), ), PartUpdated: BusEvent.define( @@ -472,7 +471,7 @@ export namespace MessageV2 { "message.part.delta", z.object({ sessionID: SessionID.zod, - messageID: z.string(), + messageID: MessageID.zod, partID: z.string(), field: z.string(), delta: z.string(), @@ -482,7 +481,7 @@ export namespace MessageV2 { "message.part.removed", z.object({ sessionID: SessionID.zod, - messageID: z.string(), + messageID: MessageID.zod, partID: z.string(), }), ), @@ -699,7 +698,7 @@ export namespace MessageV2 { // media (images, PDFs) in tool results if (media.length > 0) { result.push({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "user", parts: [ { @@ -782,7 +781,7 @@ export namespace MessageV2 { } }) - export const parts = fn(Identifier.schema("message"), async (message_id) => { + export const parts = fn(MessageID.zod, async (message_id) => { const rows = Database.use((db) => db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).orderBy(PartTable.id).all(), ) @@ -794,7 +793,7 @@ export namespace MessageV2 { export const get = fn( z.object({ sessionID: SessionID.zod, - messageID: Identifier.schema("message"), + messageID: MessageID.zod, }), async (input): Promise<WithParts> => { const row = Database.use((db) => db.select().from(MessageTable).where(eq(MessageTable.id, input.messageID)).get()) diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 33c6c9e427..c208663ca2 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -15,7 +15,7 @@ import { Config } from "@/config/config" import { SessionCompaction } from "./compaction" import { PermissionNext } from "@/permission/next" import { Question } from "@/question" -import type { SessionID } from "./schema" +import type { SessionID, MessageID } from "./schema" export namespace SessionProcessor { const DOOM_LOOP_THRESHOLD = 3 diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5fe9121486..70c620dc51 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -4,7 +4,7 @@ import fs from "fs/promises" import z from "zod" import { Filesystem } from "../util/filesystem" import { Identifier } from "../id/id" -import { SessionID } from "./schema" +import { SessionID, MessageID } from "./schema" import { MessageV2 } from "./message-v2" import { Log } from "../util/log" import { SessionRevert } from "./revert" @@ -92,7 +92,7 @@ export namespace SessionPrompt { export const PromptInput = z.object({ sessionID: SessionID.zod, - messageID: Identifier.schema("message").optional(), + messageID: MessageID.zod.optional(), model: z .object({ providerID: z.string(), @@ -355,7 +355,7 @@ export namespace SessionPrompt { const taskTool = await TaskTool.init() const taskModel = task.model ? await Provider.getModel(task.model.providerID, task.model.modelID) : model const assistantMessage = (await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "assistant", parentID: lastUser.id, sessionID, @@ -504,7 +504,7 @@ export namespace SessionPrompt { // If we create assistant messages w/ out user ones following mid loop thinking signatures // will be missing and it can cause errors for models like gemini for example const summaryUserMsg: MessageV2.User = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), sessionID, role: "user", time: { @@ -568,7 +568,7 @@ export namespace SessionPrompt { const processor = SessionProcessor.create({ assistantMessage: (await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), parentID: lastUser.id, role: "assistant", mode: agent.name, @@ -971,7 +971,7 @@ export namespace SessionPrompt { const variant = input.variant ?? (agent.variant && full?.variants?.[agent.variant] ? agent.variant : undefined) const info: MessageV2.Info = { - id: input.messageID ?? Identifier.ascending("message"), + id: input.messageID ?? MessageID.ascending(), role: "user", sessionID: input.sessionID, time: { @@ -1505,7 +1505,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const agent = await Agent.get(input.agent) const model = input.model ?? agent.model ?? (await lastModel(input.sessionID)) const userMsg: MessageV2.User = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), sessionID: input.sessionID, time: { created: Date.now(), @@ -1529,7 +1529,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the await Session.updatePart(userPart) const msg: MessageV2.Assistant = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), sessionID: input.sessionID, parentID: userMsg.id, mode: input.agent, @@ -1719,7 +1719,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the } export const CommandInput = z.object({ - messageID: Identifier.schema("message").optional(), + messageID: MessageID.zod.optional(), sessionID: SessionID.zod, agent: z.string().optional(), model: z.string().optional(), diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 9b42ab350a..2f25bdfed9 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -1,6 +1,6 @@ import z from "zod" import { Identifier } from "../id/id" -import { SessionID } from "./schema" +import { SessionID, MessageID } from "./schema" import { Snapshot } from "../snapshot" import { MessageV2 } from "./message-v2" import { Session } from "." @@ -17,7 +17,7 @@ export namespace SessionRevert { export const RevertInput = z.object({ sessionID: SessionID.zod, - messageID: Identifier.schema("message"), + messageID: MessageID.zod, partID: Identifier.schema("part").optional(), }) export type RevertInput = z.infer<typeof RevertInput> diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts index 5277f5e0b6..78a0b35ec1 100644 --- a/packages/opencode/src/session/schema.ts +++ b/packages/opencode/src/session/schema.ts @@ -15,3 +15,15 @@ export const SessionID = sessionIdSchema.pipe( zod: z.string().startsWith("ses").pipe(z.custom<SessionID>()), })), ) + +const messageIdSchema = Schema.String.pipe(Schema.brand("MessageId")) + +export type MessageID = typeof messageIdSchema.Type + +export const MessageID = messageIdSchema.pipe( + withStatics((schema: typeof messageIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("message", id)), + zod: z.string().startsWith("msg").pipe(z.custom<MessageID>()), + })), +) diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 309b623b86..7c456161e1 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -4,7 +4,8 @@ import type { MessageV2 } from "./message-v2" import type { Snapshot } from "../snapshot" import type { PermissionNext } from "../permission/next" import type { ProjectID } from "../project/schema" -import type { SessionID } from "./schema" +import type { SessionID, MessageID } from "./schema" +import type { WorkspaceID } from "../control-plane/schema" import { Timestamps } from "../storage/schema.sql" type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID"> @@ -18,7 +19,7 @@ export const SessionTable = sqliteTable( .$type<ProjectID>() .notNull() .references(() => ProjectTable.id, { onDelete: "cascade" }), - workspace_id: text(), + workspace_id: text().$type<WorkspaceID>(), parent_id: text().$type<SessionID>(), slug: text().notNull(), directory: text().notNull(), @@ -29,7 +30,7 @@ export const SessionTable = sqliteTable( summary_deletions: integer(), summary_files: integer(), summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(), - revert: text({ mode: "json" }).$type<{ messageID: string; partID?: string; snapshot?: string; diff?: string }>(), + revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: string; snapshot?: string; diff?: string }>(), permission: text({ mode: "json" }).$type<PermissionNext.Ruleset>(), ...Timestamps, time_compacting: integer(), @@ -45,7 +46,7 @@ export const SessionTable = sqliteTable( export const MessageTable = sqliteTable( "message", { - id: text().primaryKey(), + id: text().$type<MessageID>().primaryKey(), session_id: text() .$type<SessionID>() .notNull() @@ -61,6 +62,7 @@ export const PartTable = sqliteTable( { id: text().primaryKey(), message_id: text() + .$type<MessageID>() .notNull() .references(() => MessageTable.id, { onDelete: "cascade" }), session_id: text().$type<SessionID>().notNull(), diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index f489c9a628..678a008518 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -4,7 +4,7 @@ import { Session } from "." import { MessageV2 } from "./message-v2" import { Identifier } from "@/id/id" -import { SessionID } from "./schema" +import { SessionID, MessageID } from "./schema" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage/storage" @@ -70,7 +70,7 @@ export namespace SessionSummary { export const summarize = fn( z.object({ sessionID: SessionID.zod, - messageID: z.string(), + messageID: MessageID.zod, }), async (input) => { const all = await Session.messages({ sessionID: input.sessionID }) @@ -115,7 +115,7 @@ export namespace SessionSummary { export const diff = fn( z.object({ sessionID: SessionID.zod, - messageID: Identifier.schema("message").optional(), + messageID: MessageID.zod.optional(), }), async (input) => { const diffs = await Storage.read<Snapshot.FileDiff[]>(["session_diff", input.sessionID]).catch(() => []) diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index 0c25cf3e95..75ae4f5a50 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -7,7 +7,7 @@ import { MessageV2 } from "../session/message-v2" import { Identifier } from "../id/id" import { Provider } from "../provider/provider" import { Instance } from "../project/instance" -import type { SessionID } from "../session/schema" +import { type SessionID, MessageID } from "../session/schema" import EXIT_DESCRIPTION from "./plan-exit.txt" async function getLastModel(sessionID: SessionID) { @@ -45,7 +45,7 @@ export const PlanExitTool = Tool.define("plan_exit", { const model = await getLastModel(ctx.sessionID) const userMsg: MessageV2.User = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), sessionID: ctx.sessionID, role: "user", time: { @@ -103,7 +103,7 @@ export const PlanEnterTool = Tool.define("plan_enter", { const model = await getLastModel(ctx.sessionID) const userMsg: MessageV2.User = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), sessionID: ctx.sessionID, role: "user", time: { diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 2304a2e2bf..68e44eb97e 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -2,7 +2,7 @@ import { Tool } from "./tool" import DESCRIPTION from "./task.txt" import z from "zod" import { Session } from "../session" -import { SessionID } from "../session/schema" +import { SessionID, MessageID } from "../session/schema" import { MessageV2 } from "../session/message-v2" import { Identifier } from "../id/id" import { Agent } from "../agent/agent" @@ -117,7 +117,7 @@ export const TaskTool = Tool.define("task", async (ctx) => { }, }) - const messageID = Identifier.ascending("message") + const messageID = MessageID.ascending() function cancel() { SessionPrompt.cancel(session.id) diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 7f58520c56..8cc7b57d85 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -2,7 +2,7 @@ import z from "zod" import type { MessageV2 } from "../session/message-v2" import type { Agent } from "../agent/agent" import type { PermissionNext } from "../permission/next" -import type { SessionID } from "../session/schema" +import type { SessionID, MessageID } from "../session/schema" import { Truncate } from "./truncation" export namespace Tool { @@ -16,7 +16,7 @@ export namespace Tool { export type Context<M extends Metadata = Metadata> = { sessionID: SessionID - messageID: string + messageID: MessageID agent: string abort: AbortSignal callID?: string diff --git a/packages/opencode/test/cli/github-action.test.ts b/packages/opencode/test/cli/github-action.test.ts index c2bd293d79..48d7656ee3 100644 --- a/packages/opencode/test/cli/github-action.test.ts +++ b/packages/opencode/test/cli/github-action.test.ts @@ -1,14 +1,14 @@ import { test, expect, describe } from "bun:test" import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github" import type { MessageV2 } from "../../src/session/message-v2" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" // Helper to create minimal valid parts function createTextPart(text: string): MessageV2.Part { return { id: "1", sessionID: SessionID.make("s"), - messageID: "m", + messageID: MessageID.make("m"), type: "text" as const, text, } @@ -18,7 +18,7 @@ function createReasoningPart(text: string): MessageV2.Part { return { id: "1", sessionID: SessionID.make("s"), - messageID: "m", + messageID: MessageID.make("m"), type: "reasoning" as const, text, time: { start: 0 }, @@ -30,7 +30,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn return { id: "1", sessionID: SessionID.make("s"), - messageID: "m", + messageID: MessageID.make("m"), type: "tool" as const, callID: "c1", tool, @@ -47,7 +47,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn return { id: "1", sessionID: SessionID.make("s"), - messageID: "m", + messageID: MessageID.make("m"), type: "tool" as const, callID: "c1", tool, @@ -63,7 +63,7 @@ function createStepStartPart(): MessageV2.Part { return { id: "1", sessionID: SessionID.make("s"), - messageID: "m", + messageID: MessageID.make("m"), type: "step-start" as const, } } @@ -72,7 +72,7 @@ function createStepFinishPart(): MessageV2.Part { return { id: "1", sessionID: SessionID.make("s"), - messageID: "m", + messageID: MessageID.make("m"), type: "step-finish" as const, reason: "done", cost: 0, diff --git a/packages/opencode/test/control-plane/session-proxy-middleware.test.ts b/packages/opencode/test/control-plane/session-proxy-middleware.test.ts index c674d95ec7..d4d152a1c6 100644 --- a/packages/opencode/test/control-plane/session-proxy-middleware.test.ts +++ b/packages/opencode/test/control-plane/session-proxy-middleware.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, mock, test } from "bun:test" -import { Identifier } from "../../src/id/id" +import { WorkspaceID } from "../../src/control-plane/schema" import { Hono } from "hono" import { tmpdir } from "../fixture/fixture" import { Project } from "../../src/project/project" @@ -64,8 +64,8 @@ async function setup(state: State) { await using tmp = await tmpdir({ git: true }) const { project } = await Project.fromDirectory(tmp.path) - const id1 = Identifier.descending("workspace") - const id2 = Identifier.descending("workspace") + const id1 = WorkspaceID.ascending() + const id2 = WorkspaceID.ascending() Database.use((db) => db diff --git a/packages/opencode/test/control-plane/workspace-sync.test.ts b/packages/opencode/test/control-plane/workspace-sync.test.ts index 899118920f..0f8d608fb3 100644 --- a/packages/opencode/test/control-plane/workspace-sync.test.ts +++ b/packages/opencode/test/control-plane/workspace-sync.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, mock, test } from "bun:test" -import { Identifier } from "../../src/id/id" +import { WorkspaceID } from "../../src/control-plane/schema" import { Log } from "../../src/util/log" import { tmpdir } from "../fixture/fixture" import { Project } from "../../src/project/project" @@ -52,8 +52,8 @@ describe("control-plane/workspace.startSyncing", () => { await using tmp = await tmpdir({ git: true }) const { project } = await Project.fromDirectory(tmp.path) - const id1 = Identifier.descending("workspace") - const id2 = Identifier.descending("workspace") + const id1 = WorkspaceID.ascending() + const id2 = WorkspaceID.ascending() Database.use((db) => db diff --git a/packages/opencode/test/memory/abort-leak.test.ts b/packages/opencode/test/memory/abort-leak.test.ts index 68770bf17a..eebb651a53 100644 --- a/packages/opencode/test/memory/abort-leak.test.ts +++ b/packages/opencode/test/memory/abort-leak.test.ts @@ -2,13 +2,13 @@ import { describe, test, expect } from "bun:test" import path from "path" import { Instance } from "../../src/project/instance" import { WebFetchTool } from "../../src/tool/webfetch" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const projectRoot = path.join(__dirname, "../..") const ctx = { sessionID: SessionID.make("ses_test"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: new AbortController().signal, diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 871fd8cb57..df9dedb356 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -11,7 +11,7 @@ import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" import type { Agent } from "../../src/agent/agent" import type { MessageV2 } from "../../src/session/message-v2" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" describe("session.llm.hasToolCalls", () => { test("returns false for empty messages array", () => { @@ -277,7 +277,7 @@ describe("session.llm.stream", () => { } satisfies Agent.Info const user = { - id: "user-1", + id: MessageID.make("user-1"), sessionID, role: "user", time: { created: Date.now() }, @@ -406,7 +406,7 @@ describe("session.llm.stream", () => { } satisfies Agent.Info const user = { - id: "user-2", + id: MessageID.make("user-2"), sessionID, role: "user", time: { created: Date.now() }, @@ -529,7 +529,7 @@ describe("session.llm.stream", () => { } satisfies Agent.Info const user = { - id: "user-3", + id: MessageID.make("user-3"), sessionID, role: "user", time: { created: Date.now() }, @@ -630,7 +630,7 @@ describe("session.llm.stream", () => { } satisfies Agent.Info const user = { - id: "user-4", + id: MessageID.make("user-4"), sessionID, role: "user", time: { created: Date.now() }, diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 158d341f9e..af1d0018bc 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import { APICallError } from "ai" import { MessageV2 } from "../../src/session/message-v2" import type { Provider } from "../../src/provider/provider" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const sessionID = SessionID.make("session") const model: Provider.Model = { @@ -100,7 +100,7 @@ function basePart(messageID: string, id: string) { return { id, sessionID, - messageID, + messageID: MessageID.make(messageID), } } diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index de2b14573f..37b3e21771 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -7,6 +7,7 @@ import { MessageV2 } from "../../src/session/message-v2" import { Log } from "../../src/util/log" import { Instance } from "../../src/project/instance" import { Identifier } from "../../src/id/id" +import { MessageID } from "../../src/session/schema" import { tmpdir } from "../fixture/fixture" const projectRoot = path.join(__dirname, "../..") @@ -24,7 +25,7 @@ describe("revert + compact workflow", () => { // Create a user message const userMsg1 = await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "user", sessionID, agent: "default", @@ -48,7 +49,7 @@ describe("revert + compact workflow", () => { // Create an assistant response message const assistantMsg1: MessageV2.Assistant = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "assistant", sessionID, mode: "default", @@ -85,7 +86,7 @@ describe("revert + compact workflow", () => { // Create another user message const userMsg2 = await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "user", sessionID, agent: "default", @@ -108,7 +109,7 @@ describe("revert + compact workflow", () => { // Create another assistant response const assistantMsg2: MessageV2.Assistant = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "assistant", sessionID, mode: "default", @@ -200,7 +201,7 @@ describe("revert + compact workflow", () => { // Create initial messages const userMsg = await Session.updateMessage({ - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "user", sessionID, agent: "default", @@ -222,7 +223,7 @@ describe("revert + compact workflow", () => { }) const assistantMsg: MessageV2.Assistant = { - id: Identifier.ascending("message"), + id: MessageID.ascending(), role: "assistant", sessionID, mode: "default", diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index aa9ca05d04..f5512f240e 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -6,6 +6,7 @@ import { Log } from "../../src/util/log" import { Instance } from "../../src/project/instance" import { MessageV2 } from "../../src/session/message-v2" import { Identifier } from "../../src/id/id" +import { MessageID } from "../../src/session/schema" const projectRoot = path.join(__dirname, "../..") Log.init({ print: false }) @@ -81,7 +82,7 @@ describe("step-finish token propagation via Bus event", () => { fn: async () => { const session = await Session.create({}) - const messageID = Identifier.ascending("message") + const messageID = MessageID.ascending() await Session.updateMessage({ id: messageID, sessionID: session.id, diff --git a/packages/opencode/test/session/structured-output.test.ts b/packages/opencode/test/session/structured-output.test.ts index 537df07af6..f6131b149b 100644 --- a/packages/opencode/test/session/structured-output.test.ts +++ b/packages/opencode/test/session/structured-output.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test" import { MessageV2 } from "../../src/session/message-v2" import { SessionPrompt } from "../../src/session/prompt" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" describe("structured-output.OutputFormat", () => { test("parses text format", () => { @@ -96,7 +96,7 @@ describe("structured-output.StructuredOutputError", () => { describe("structured-output.UserMessage", () => { test("user message accepts outputFormat", () => { const result = MessageV2.User.safeParse({ - id: "test-id", + id: MessageID.ascending(), sessionID: SessionID.descending(), role: "user", time: { created: Date.now() }, @@ -112,7 +112,7 @@ describe("structured-output.UserMessage", () => { test("user message works without outputFormat (optional)", () => { const result = MessageV2.User.safeParse({ - id: "test-id", + id: MessageID.ascending(), sessionID: SessionID.descending(), role: "user", time: { created: Date.now() }, @@ -125,10 +125,10 @@ describe("structured-output.UserMessage", () => { describe("structured-output.AssistantMessage", () => { const baseAssistantMessage = { - id: "test-id", + id: MessageID.ascending(), sessionID: SessionID.descending(), role: "assistant" as const, - parentID: "parent-id", + parentID: MessageID.ascending(), modelID: "claude-3", providerID: "anthropic", mode: "default", diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts index f5f8cbd5cd..f436037158 100644 --- a/packages/opencode/test/storage/json-migration.test.ts +++ b/packages/opencode/test/storage/json-migration.test.ts @@ -11,7 +11,7 @@ import { ProjectTable } from "../../src/project/project.sql" import { ProjectID } from "../../src/project/schema" import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql" import { SessionShareTable } from "../../src/share/share.sql" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" // Test fixtures const fixtures = { @@ -255,7 +255,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const messages = db.select().from(MessageTable).all() expect(messages.length).toBe(1) - expect(messages[0].id).toBe("msg_test789ghi") + expect(messages[0].id).toBe(MessageID.make("msg_test789ghi")) const parts = db.select().from(PartTable).all() expect(parts.length).toBe(1) @@ -295,7 +295,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const messages = db.select().from(MessageTable).all() expect(messages.length).toBe(1) - expect(messages[0].id).toBe("msg_test789ghi") + expect(messages[0].id).toBe(MessageID.make("msg_test789ghi")) expect(messages[0].session_id).toBe(SessionID.make("ses_test456def")) expect(messages[0].data).not.toHaveProperty("id") expect(messages[0].data).not.toHaveProperty("sessionID") @@ -303,7 +303,7 @@ describe("JSON to SQLite migration", () => { const parts = db.select().from(PartTable).all() expect(parts.length).toBe(1) expect(parts[0].id).toBe("prt_testabc123") - expect(parts[0].message_id).toBe("msg_test789ghi") + expect(parts[0].message_id).toBe(MessageID.make("msg_test789ghi")) expect(parts[0].session_id).toBe(SessionID.make("ses_test456def")) expect(parts[0].data).not.toHaveProperty("id") expect(parts[0].data).not.toHaveProperty("messageID") @@ -336,7 +336,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const messages = db.select().from(MessageTable).all() expect(messages.length).toBe(1) - expect(messages[0].id).toBe("msg_from_filename") // Uses filename, not JSON id + expect(messages[0].id).toBe(MessageID.make("msg_from_filename")) // Uses filename, not JSON id expect(messages[0].session_id).toBe(SessionID.make("ses_test456def")) }) @@ -375,7 +375,7 @@ describe("JSON to SQLite migration", () => { const parts = db.select().from(PartTable).all() expect(parts.length).toBe(1) expect(parts[0].id).toBe("prt_from_filename") // Uses filename, not JSON id - expect(parts[0].message_id).toBe("msg_realmsgid") // Uses parent dir, not JSON messageID + expect(parts[0].message_id).toBe(MessageID.make("msg_realmsgid")) // Uses parent dir, not JSON messageID }) test("skips orphaned sessions (no parent project)", async () => { diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index 008b4fa49c..4e276517f1 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -4,11 +4,11 @@ import * as fs from "fs/promises" import { ApplyPatchTool } from "../../src/tool/apply_patch" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const baseCtx = { sessionID: SessionID.make("ses_test"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index d4a6c7ccdb..f947398b37 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -7,11 +7,11 @@ import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" import type { PermissionNext } from "../../src/permission/next" import { Truncate } from "../../src/tool/truncation" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const ctx = { sessionID: SessionID.make("ses_test"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index 601995889e..b0ee95ff6f 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -5,11 +5,11 @@ import { EditTool } from "../../src/tool/edit" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import { FileTime } from "../../src/file/time" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const ctx = { sessionID: SessionID.make("ses_test-edit-session"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index fe33d69151..58e53e5839 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -4,11 +4,11 @@ import type { Tool } from "../../src/tool/tool" import { Instance } from "../../src/project/instance" import { assertExternalDirectory } from "../../src/tool/external-directory" import type { PermissionNext } from "../../src/permission/next" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const baseCtx: Omit<Tool.Context, "ask"> = { sessionID: SessionID.make("ses_test"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 5823553722..e03b1752ec 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -3,11 +3,11 @@ import path from "path" import { GrepTool } from "../../src/tool/grep" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const ctx = { sessionID: SessionID.make("ses_test"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index 69e211abdd..9157aaa9a4 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -2,11 +2,11 @@ import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test" import { z } from "zod" import { QuestionTool } from "../../src/tool/question" import * as QuestionModule from "../../src/question" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const ctx = { sessionID: SessionID.make("ses_test-session"), - messageID: "test-message", + messageID: MessageID.make("test-message"), callID: "test-call", agent: "test-agent", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 45f3c1b32e..7659d690c3 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -6,13 +6,13 @@ import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" import { PermissionNext } from "../../src/permission/next" import { Agent } from "../../src/agent/agent" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const FIXTURES_DIR = path.join(import.meta.dir, "fixtures") const ctx = { sessionID: SessionID.make("ses_test"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index fa67c293e9..5bcdb6c2b9 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -6,11 +6,11 @@ import type { Tool } from "../../src/tool/tool" import { Instance } from "../../src/project/instance" import { SkillTool } from "../../src/tool/skill" import { tmpdir } from "../fixture/fixture" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const baseCtx: Omit<Tool.Context, "ask"> = { sessionID: SessionID.make("ses_test"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index 769a61e165..088f3dd16d 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -2,13 +2,13 @@ import { describe, expect, test } from "bun:test" import path from "path" import { Instance } from "../../src/project/instance" import { WebFetchTool } from "../../src/tool/webfetch" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const projectRoot = path.join(import.meta.dir, "../..") const ctx = { sessionID: SessionID.make("ses_test"), - messageID: "message", + messageID: MessageID.make("message"), callID: "", agent: "build", abort: AbortSignal.any([]), diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 62279057a9..b93ab4e853 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -4,11 +4,11 @@ import fs from "fs/promises" import { WriteTool } from "../../src/tool/write" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" -import { SessionID } from "../../src/session/schema" +import { SessionID, MessageID } from "../../src/session/schema" const ctx = { sessionID: SessionID.make("ses_test-write-session"), - messageID: "", + messageID: MessageID.make(""), callID: "", agent: "build", abort: AbortSignal.any([]), From d26c6f80e10dece9a77e589fdaf9fa1709dfe0ed Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:31:07 +0000 Subject: [PATCH 112/200] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 18 ----- packages/sdk/openapi.json | 102 ++++++++++++++---------- 2 files changed, 62 insertions(+), 58 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 8d6b04503b..30568c96df 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3321,9 +3321,6 @@ export type SessionDeleteMessageData = { body?: never path: { sessionID: string - /** - * Message ID - */ messageID: string } query?: { @@ -3359,9 +3356,6 @@ export type SessionMessageData = { body?: never path: { sessionID: string - /** - * Message ID - */ messageID: string } query?: { @@ -3400,13 +3394,7 @@ export type PartDeleteData = { body?: never path: { sessionID: string - /** - * Message ID - */ messageID: string - /** - * Part ID - */ partID: string } query?: { @@ -3442,13 +3430,7 @@ export type PartUpdateData = { body?: Part path: { sessionID: string - /** - * Message ID - */ messageID: string - /** - * Part ID - */ partID: string } query?: { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 7b0ad181a3..33e4927bf0 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -3076,10 +3076,10 @@ "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true } ], "summary": "Get message", @@ -3165,10 +3165,10 @@ "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true } ], "summary": "Delete message", @@ -3244,10 +3244,10 @@ "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true }, { "in": "path", @@ -3255,8 +3255,7 @@ "schema": { "type": "string" }, - "required": true, - "description": "Part ID" + "required": true } ], "description": "Delete a part from a message", @@ -3329,10 +3328,10 @@ "in": "path", "name": "messageID", "schema": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, - "required": true, - "description": "Message ID" + "required": true }, { "in": "path", @@ -3340,8 +3339,7 @@ "schema": { "type": "string" }, - "required": true, - "description": "Part ID" + "required": true } ], "description": "Update a part in a message", @@ -7201,7 +7199,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "sessionID": { "type": "string", @@ -7442,7 +7441,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "sessionID": { "type": "string", @@ -7490,7 +7490,8 @@ ] }, "parentID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "modelID": { "type": "string" @@ -7619,7 +7620,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" } }, "required": ["sessionID", "messageID"] @@ -7638,7 +7640,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -7686,7 +7689,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -7730,7 +7734,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -7896,7 +7901,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8090,7 +8096,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8126,7 +8133,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8149,7 +8157,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8208,7 +8217,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8231,7 +8241,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8260,7 +8271,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8302,7 +8314,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8337,7 +8350,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "type": { "type": "string", @@ -8426,7 +8440,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { "type": "string" @@ -8458,7 +8473,8 @@ "pattern": "^ses.*" }, "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { "type": "string" @@ -8506,7 +8522,8 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "callID": { "type": "string" @@ -8708,7 +8725,8 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "callID": { "type": "string" @@ -9114,7 +9132,8 @@ "type": "string" }, "workspaceID": { - "type": "string" + "type": "string", + "pattern": "^wrk.*" }, "directory": { "type": "string" @@ -9184,7 +9203,8 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { "type": "string" @@ -11201,7 +11221,8 @@ "type": "string" }, "workspaceID": { - "type": "string" + "type": "string", + "pattern": "^wrk.*" }, "directory": { "type": "string" @@ -11271,7 +11292,8 @@ "type": "object", "properties": { "messageID": { - "type": "string" + "type": "string", + "pattern": "^msg.*" }, "partID": { "type": "string" From 090f6363543c426a477fcb706bc2bfac1e0f6140 Mon Sep 17 00:00:00 2001 From: Kit Langton <kit.langton@gmail.com> Date: Wed, 11 Mar 2026 19:40:50 -0400 Subject: [PATCH 113/200] feat(id): brand PartID through Drizzle and Zod schemas (#16966) --- packages/opencode/script/seed-e2e.ts | 5 ++-- packages/opencode/src/cli/cmd/debug/agent.ts | 5 ++-- packages/opencode/src/cli/cmd/github.ts | 9 +++---- packages/opencode/src/cli/cmd/import.ts | 10 ++++--- .../cli/cmd/tui/component/prompt/index.tsx | 9 +++---- .../opencode/src/server/routes/session.ts | 6 ++--- packages/opencode/src/session/compaction.ts | 9 +++---- packages/opencode/src/session/index.ts | 11 ++++---- packages/opencode/src/session/message-v2.ts | 8 +++--- packages/opencode/src/session/processor.ts | 16 +++++------ packages/opencode/src/session/prompt.ts | 27 +++++++++---------- packages/opencode/src/session/revert.ts | 5 ++-- packages/opencode/src/session/schema.ts | 12 +++++++++ packages/opencode/src/session/session.sql.ts | 6 ++--- packages/opencode/src/tool/batch.ts | 8 +++--- packages/opencode/src/tool/plan.ts | 7 +++-- .../opencode/test/cli/github-action.test.ts | 14 +++++----- .../opencode/test/session/message-v2.test.ts | 4 +-- .../test/session/revert-compact.test.ts | 15 +++++------ .../opencode/test/session/session.test.ts | 5 ++-- .../test/storage/json-migration.test.ts | 8 +++--- 21 files changed, 102 insertions(+), 97 deletions(-) diff --git a/packages/opencode/script/seed-e2e.ts b/packages/opencode/script/seed-e2e.ts index d233341448..78bbca2866 100644 --- a/packages/opencode/script/seed-e2e.ts +++ b/packages/opencode/script/seed-e2e.ts @@ -11,8 +11,7 @@ const seed = async () => { const { Instance } = await import("../src/project/instance") const { InstanceBootstrap } = await import("../src/project/bootstrap") const { Session } = await import("../src/session") - const { Identifier } = await import("../src/id/id") - const { MessageID } = await import("../src/session/schema") + const { MessageID, PartID } = await import("../src/session/schema") const { Project } = await import("../src/project/project") await Instance.provide({ @@ -21,7 +20,7 @@ const seed = async () => { fn: async () => { const session = await Session.create({ title }) const messageID = MessageID.ascending() - const partID = Identifier.descending("part") + const partID = PartID.ascending() const message = { id: messageID, sessionID: session.id, diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index 47e1d36373..297a7ec021 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/packages/opencode/src/cli/cmd/debug/agent.ts @@ -4,8 +4,7 @@ import { Agent } from "../../../agent/agent" import { Provider } from "../../../provider/provider" import { Session } from "../../../session" import type { MessageV2 } from "../../../session/message-v2" -import { Identifier } from "../../../id/id" -import { MessageID } from "../../../session/schema" +import { MessageID, PartID } from "../../../session/schema" import { ToolRegistry } from "../../../tool/registry" import { Instance } from "../../../project/instance" import { PermissionNext } from "../../../permission/next" @@ -151,7 +150,7 @@ async function createToolContext(agent: Agent.Info) { return { sessionID: session.id, messageID, - callID: Identifier.ascending("part"), + callID: PartID.ascending(), agent: agent.name, abort: new AbortController().signal, messages: [], diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index af9ff4a428..edd9d75610 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -23,8 +23,7 @@ import { Instance } from "@/project/instance" import { bootstrap } from "../bootstrap" import { Session } from "../../session" import type { SessionID } from "../../session/schema" -import { Identifier } from "../../id/id" -import { MessageID } from "../../session/schema" +import { MessageID, PartID } from "../../session/schema" import { Provider } from "../../provider/provider" import { Bus } from "../../bus" import { MessageV2 } from "../../session/message-v2" @@ -945,13 +944,13 @@ export const GithubRunCommand = cmd({ // agent is omitted - server will use default_agent from config or fall back to "build" parts: [ { - id: Identifier.ascending("part"), + id: PartID.ascending(), type: "text", text: message, }, ...files.flatMap((f) => [ { - id: Identifier.ascending("part"), + id: PartID.ascending(), type: "file" as const, mime: f.mime, url: `data:${f.mime};base64,${f.content}`, @@ -999,7 +998,7 @@ export const GithubRunCommand = cmd({ tools: { "*": false }, // Disable all tools to force text response parts: [ { - id: Identifier.ascending("part"), + id: PartID.ascending(), type: "text", text: "Summarize the actions (tool calls & reasoning) you did for the user in 1-2 sentences.", }, diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index a5a21d1c3d..8bd24cfec1 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -1,7 +1,7 @@ import type { Argv } from "yargs" import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2" import { Session } from "../../session" -import { SessionID, MessageID } from "../../session/schema" +import { SessionID, MessageID, PartID } from "../../session/schema" import { WorkspaceID } from "../../control-plane/schema" import { cmd } from "./cmd" import { bootstrap } from "../bootstrap" @@ -161,7 +161,11 @@ export const ImportCommand = cmd({ workspaceID: exportData.info.workspaceID ? WorkspaceID.make(exportData.info.workspaceID) : undefined, projectID: Instance.project.id, revert: exportData.info.revert - ? { ...exportData.info.revert, messageID: MessageID.make(exportData.info.revert.messageID) } + ? { + ...exportData.info.revert, + messageID: MessageID.make(exportData.info.revert.messageID), + partID: exportData.info.revert.partID ? PartID.make(exportData.info.revert.partID) : undefined, + } : undefined, }) Database.use((db) => @@ -193,7 +197,7 @@ export const ImportCommand = cmd({ db .insert(PartTable) .values({ - id: part.id, + id: PartID.make(part.id), message_id: MessageID.make(msg.info.id), session_id: row.id, data: partData, diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 9923cd3fab..c85426cc24 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -9,8 +9,7 @@ import { EmptyBorder } from "@tui/component/border" import { useSDK } from "@tui/context/sdk" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" -import { Identifier } from "@/id/id" -import { MessageID } from "@/session/schema" +import { MessageID, PartID } from "@/session/schema" import { createStore, produce } from "solid-js/store" import { useKeybind } from "@tui/context/keybind" import { usePromptHistory, type PromptInfo } from "./history" @@ -625,7 +624,7 @@ export function Prompt(props: PromptProps) { parts: nonTextParts .filter((x) => x.type === "file") .map((x) => ({ - id: Identifier.ascending("part"), + id: PartID.ascending(), ...x, })), }) @@ -640,12 +639,12 @@ export function Prompt(props: PromptProps) { variant, parts: [ { - id: Identifier.ascending("part"), + id: PartID.ascending(), type: "text", text: inputText, }, ...nonTextParts.map((x) => ({ - id: Identifier.ascending("part"), + id: PartID.ascending(), ...x, })), ], diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 26468c52c5..39dd1b580e 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -1,7 +1,7 @@ import { Hono } from "hono" import { stream } from "hono/streaming" import { describeRoute, validator, resolver } from "hono-openapi" -import { SessionID, MessageID } from "@/session/schema" +import { SessionID, MessageID, PartID } from "@/session/schema" import z from "zod" import { Session } from "../../session" import { MessageV2 } from "../../session/message-v2" @@ -677,7 +677,7 @@ export const SessionRoutes = lazy(() => z.object({ sessionID: SessionID.zod, messageID: MessageID.zod, - partID: z.string(), + partID: PartID.zod, }), ), async (c) => { @@ -712,7 +712,7 @@ export const SessionRoutes = lazy(() => z.object({ sessionID: SessionID.zod, messageID: MessageID.zod, - partID: z.string(), + partID: PartID.zod, }), ), validator("json", MessageV2.Part), diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 37a7cad84d..1946eeee96 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -1,8 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { Session } from "." -import { Identifier } from "../id/id" -import { SessionID, MessageID } from "./schema" +import { SessionID, MessageID, PartID } from "./schema" import { Instance } from "../project/instance" import { Provider } from "../provider/provider" import { MessageV2 } from "./message-v2" @@ -256,7 +255,7 @@ When constructing the summary, try to stick to this template: : part await Session.updatePart({ ...replayPart, - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: replayMsg.id, sessionID: input.sessionID, }) @@ -276,7 +275,7 @@ When constructing the summary, try to stick to this template: : "") + "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed." await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: continueMsg.id, sessionID: input.sessionID, type: "text", @@ -317,7 +316,7 @@ When constructing the summary, try to stick to this template: }, }) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: msg.id, sessionID: msg.sessionID, type: "compaction", diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 63495cd00c..275b50e42b 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -7,7 +7,6 @@ import z from "zod" import { type ProviderMetadata } from "ai" import { Config } from "../config/config" import { Flag } from "../flag/flag" -import { Identifier } from "../id/id" import { Installation } from "../installation" import { Database, NotFoundError, eq, and, or, gte, isNull, desc, like, inArray, lt } from "../storage/db" @@ -25,7 +24,7 @@ import { Snapshot } from "@/snapshot" import { WorkspaceContext } from "../control-plane/workspace-context" import { ProjectID } from "../project/schema" import { WorkspaceID } from "../control-plane/schema" -import { SessionID, MessageID } from "./schema" +import { SessionID, MessageID, PartID } from "./schema" import type { Provider } from "@/provider/provider" import { PermissionNext } from "@/permission/next" @@ -152,7 +151,7 @@ export namespace Session { revert: z .object({ messageID: MessageID.zod, - partID: z.string().optional(), + partID: PartID.zod.optional(), snapshot: z.string().optional(), diff: z.string().optional(), }) @@ -269,7 +268,7 @@ export namespace Session { for (const part of msg.parts) { await updatePart({ ...part, - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: cloned.id, sessionID: session.id, }) @@ -731,7 +730,7 @@ export namespace Session { z.object({ sessionID: SessionID.zod, messageID: MessageID.zod, - partID: Identifier.schema("part"), + partID: PartID.zod, }), async (input) => { Database.use((db) => { @@ -779,7 +778,7 @@ export namespace Session { z.object({ sessionID: SessionID.zod, messageID: MessageID.zod, - partID: z.string(), + partID: PartID.zod, field: z.string(), delta: z.string(), }), diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 0fa842b299..90abf54526 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,5 +1,5 @@ import { BusEvent } from "@/bus/bus-event" -import { SessionID, MessageID } from "./schema" +import { SessionID, MessageID, PartID } from "./schema" import z from "zod" import { NamedError } from "@opencode-ai/util/error" import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" @@ -78,7 +78,7 @@ export namespace MessageV2 { export type OutputFormat = z.infer<typeof Format> const PartBase = z.object({ - id: z.string(), + id: PartID.zod, sessionID: SessionID.zod, messageID: MessageID.zod, }) @@ -472,7 +472,7 @@ export namespace MessageV2 { z.object({ sessionID: SessionID.zod, messageID: MessageID.zod, - partID: z.string(), + partID: PartID.zod, field: z.string(), delta: z.string(), }), @@ -482,7 +482,7 @@ export namespace MessageV2 { z.object({ sessionID: SessionID.zod, messageID: MessageID.zod, - partID: z.string(), + partID: PartID.zod, }), ), } diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index c208663ca2..38dac41b05 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -1,6 +1,5 @@ import { MessageV2 } from "./message-v2" import { Log } from "@/util/log" -import { Identifier } from "@/id/id" import { Session } from "." import { Agent } from "@/agent/agent" import { Snapshot } from "@/snapshot" @@ -15,6 +14,7 @@ import { Config } from "@/config/config" import { SessionCompaction } from "./compaction" import { PermissionNext } from "@/permission/next" import { Question } from "@/question" +import { PartID } from "./schema" import type { SessionID, MessageID } from "./schema" export namespace SessionProcessor { @@ -65,7 +65,7 @@ export namespace SessionProcessor { continue } const reasoningPart = { - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: input.assistantMessage.id, sessionID: input.assistantMessage.sessionID, type: "reasoning" as const, @@ -111,7 +111,7 @@ export namespace SessionProcessor { case "tool-input-start": const part = await Session.updatePart({ - id: toolcalls[value.id]?.id ?? Identifier.ascending("part"), + id: toolcalls[value.id]?.id ?? PartID.ascending(), messageID: input.assistantMessage.id, sessionID: input.assistantMessage.sessionID, type: "tool", @@ -234,7 +234,7 @@ export namespace SessionProcessor { case "start-step": snapshot = await Snapshot.track() await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: input.assistantMessage.id, sessionID: input.sessionID, snapshot, @@ -252,7 +252,7 @@ export namespace SessionProcessor { input.assistantMessage.cost += usage.cost input.assistantMessage.tokens = usage.tokens await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), reason: value.finishReason, snapshot: await Snapshot.track(), messageID: input.assistantMessage.id, @@ -266,7 +266,7 @@ export namespace SessionProcessor { const patch = await Snapshot.patch(snapshot) if (patch.files.length) { await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: input.assistantMessage.id, sessionID: input.sessionID, type: "patch", @@ -290,7 +290,7 @@ export namespace SessionProcessor { case "text-start": currentText = { - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: input.assistantMessage.id, sessionID: input.assistantMessage.sessionID, type: "text", @@ -389,7 +389,7 @@ export namespace SessionProcessor { const patch = await Snapshot.patch(snapshot) if (patch.files.length) { await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: input.assistantMessage.id, sessionID: input.sessionID, type: "patch", diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 70c620dc51..b8be93b6be 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -3,8 +3,7 @@ import os from "os" import fs from "fs/promises" import z from "zod" import { Filesystem } from "../util/filesystem" -import { Identifier } from "../id/id" -import { SessionID, MessageID } from "./schema" +import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" import { Log } from "../util/log" import { SessionRevert } from "./revert" @@ -380,7 +379,7 @@ export namespace SessionPrompt { }, })) as MessageV2.Assistant let part = (await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: assistantMessage.id, sessionID: assistantMessage.sessionID, type: "tool", @@ -449,7 +448,7 @@ export namespace SessionPrompt { }) const attachments = result?.attachments?.map((attachment) => ({ ...attachment, - id: Identifier.ascending("part"), + id: PartID.ascending(), sessionID, messageID: assistantMessage.id, })) @@ -515,7 +514,7 @@ export namespace SessionPrompt { } await Session.updateMessage(summaryUserMsg) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: summaryUserMsg.id, sessionID, type: "text", @@ -814,7 +813,7 @@ export namespace SessionPrompt { ...result, attachments: result.attachments?.map((attachment) => ({ ...attachment, - id: Identifier.ascending("part"), + id: PartID.ascending(), sessionID: ctx.sessionID, messageID: input.processor.message.id, })), @@ -917,7 +916,7 @@ export namespace SessionPrompt { output: truncated.content, attachments: attachments.map((attachment) => ({ ...attachment, - id: Identifier.ascending("part"), + id: PartID.ascending(), sessionID: ctx.sessionID, messageID: input.processor.message.id, })), @@ -989,7 +988,7 @@ export namespace SessionPrompt { type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({ ...part, - id: part.id ?? Identifier.ascending("part"), + id: part.id ? PartID.make(part.id) : PartID.ascending(), }) const parts = await Promise.all( @@ -1335,7 +1334,7 @@ export namespace SessionPrompt { if (!Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE) { if (input.agent.name === "plan") { userMessage.parts.push({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMessage.info.id, sessionID: userMessage.info.sessionID, type: "text", @@ -1346,7 +1345,7 @@ export namespace SessionPrompt { const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan") if (wasPlan && input.agent.name === "build") { userMessage.parts.push({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMessage.info.id, sessionID: userMessage.info.sessionID, type: "text", @@ -1366,7 +1365,7 @@ export namespace SessionPrompt { const exists = await Filesystem.exists(plan) if (exists) { const part = await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMessage.info.id, sessionID: userMessage.info.sessionID, type: "text", @@ -1385,7 +1384,7 @@ export namespace SessionPrompt { const exists = await Filesystem.exists(plan) if (!exists) await fs.mkdir(path.dirname(plan), { recursive: true }) const part = await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMessage.info.id, sessionID: userMessage.info.sessionID, type: "text", @@ -1520,7 +1519,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the await Session.updateMessage(userMsg) const userPart: MessageV2.Part = { type: "text", - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMsg.id, sessionID: input.sessionID, text: "The following tool was executed by the user", @@ -1555,7 +1554,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the await Session.updateMessage(msg) const part: MessageV2.Part = { type: "tool", - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: msg.id, sessionID: input.sessionID, tool: "bash", diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 2f25bdfed9..c5c9edbbdf 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -1,6 +1,5 @@ import z from "zod" -import { Identifier } from "../id/id" -import { SessionID, MessageID } from "./schema" +import { SessionID, MessageID, PartID } from "./schema" import { Snapshot } from "../snapshot" import { MessageV2 } from "./message-v2" import { Session } from "." @@ -18,7 +17,7 @@ export namespace SessionRevert { export const RevertInput = z.object({ sessionID: SessionID.zod, messageID: MessageID.zod, - partID: Identifier.schema("part").optional(), + partID: PartID.zod.optional(), }) export type RevertInput = z.infer<typeof RevertInput> diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts index 78a0b35ec1..6504a1b393 100644 --- a/packages/opencode/src/session/schema.ts +++ b/packages/opencode/src/session/schema.ts @@ -27,3 +27,15 @@ export const MessageID = messageIdSchema.pipe( zod: z.string().startsWith("msg").pipe(z.custom<MessageID>()), })), ) + +const partIdSchema = Schema.String.pipe(Schema.brand("PartId")) + +export type PartID = typeof partIdSchema.Type + +export const PartID = partIdSchema.pipe( + withStatics((schema: typeof partIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("part", id)), + zod: z.string().startsWith("prt").pipe(z.custom<PartID>()), + })), +) diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 7c456161e1..71abc8516b 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -4,7 +4,7 @@ import type { MessageV2 } from "./message-v2" import type { Snapshot } from "../snapshot" import type { PermissionNext } from "../permission/next" import type { ProjectID } from "../project/schema" -import type { SessionID, MessageID } from "./schema" +import type { SessionID, MessageID, PartID } from "./schema" import type { WorkspaceID } from "../control-plane/schema" import { Timestamps } from "../storage/schema.sql" @@ -30,7 +30,7 @@ export const SessionTable = sqliteTable( summary_deletions: integer(), summary_files: integer(), summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(), - revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: string; snapshot?: string; diff?: string }>(), + revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: PartID; snapshot?: string; diff?: string }>(), permission: text({ mode: "json" }).$type<PermissionNext.Ruleset>(), ...Timestamps, time_compacting: integer(), @@ -60,7 +60,7 @@ export const MessageTable = sqliteTable( export const PartTable = sqliteTable( "part", { - id: text().primaryKey(), + id: text().$type<PartID>().primaryKey(), message_id: text() .$type<MessageID>() .notNull() diff --git a/packages/opencode/src/tool/batch.ts b/packages/opencode/src/tool/batch.ts index eecbfe2990..0e864b021b 100644 --- a/packages/opencode/src/tool/batch.ts +++ b/packages/opencode/src/tool/batch.ts @@ -31,7 +31,7 @@ export const BatchTool = Tool.define("batch", async () => { }, async execute(params, ctx) { const { Session } = await import("../session") - const { Identifier } = await import("../id/id") + const { PartID } = await import("../session/schema") const toolCalls = params.tool_calls.slice(0, 25) const discardedCalls = params.tool_calls.slice(25) @@ -42,7 +42,7 @@ export const BatchTool = Tool.define("batch", async () => { const executeCall = async (call: (typeof toolCalls)[0]) => { const callStartTime = Date.now() - const partID = Identifier.ascending("part") + const partID = PartID.ascending() try { if (DISALLOWED.has(call.tool)) { @@ -79,7 +79,7 @@ export const BatchTool = Tool.define("batch", async () => { const result = await tool.execute(validatedParams, { ...ctx, callID: partID }) const attachments = result.attachments?.map((attachment) => ({ ...attachment, - id: Identifier.ascending("part"), + id: PartID.ascending(), sessionID: ctx.sessionID, messageID: ctx.messageID, })) @@ -134,7 +134,7 @@ export const BatchTool = Tool.define("batch", async () => { // Add discarded calls as errors const now = Date.now() for (const call of discardedCalls) { - const partID = Identifier.ascending("part") + const partID = PartID.ascending() await Session.updatePart({ id: partID, messageID: ctx.messageID, diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index 75ae4f5a50..e91bc3faa2 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -4,10 +4,9 @@ import { Tool } from "./tool" import { Question } from "../question" import { Session } from "../session" import { MessageV2 } from "../session/message-v2" -import { Identifier } from "../id/id" import { Provider } from "../provider/provider" import { Instance } from "../project/instance" -import { type SessionID, MessageID } from "../session/schema" +import { type SessionID, MessageID, PartID } from "../session/schema" import EXIT_DESCRIPTION from "./plan-exit.txt" async function getLastModel(sessionID: SessionID) { @@ -56,7 +55,7 @@ export const PlanExitTool = Tool.define("plan_exit", { } await Session.updateMessage(userMsg) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMsg.id, sessionID: ctx.sessionID, type: "text", @@ -114,7 +113,7 @@ export const PlanEnterTool = Tool.define("plan_enter", { } await Session.updateMessage(userMsg) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMsg.id, sessionID: ctx.sessionID, type: "text", diff --git a/packages/opencode/test/cli/github-action.test.ts b/packages/opencode/test/cli/github-action.test.ts index 48d7656ee3..279ed27d08 100644 --- a/packages/opencode/test/cli/github-action.test.ts +++ b/packages/opencode/test/cli/github-action.test.ts @@ -1,12 +1,12 @@ import { test, expect, describe } from "bun:test" import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github" import type { MessageV2 } from "../../src/session/message-v2" -import { SessionID, MessageID } from "../../src/session/schema" +import { SessionID, MessageID, PartID } from "../../src/session/schema" // Helper to create minimal valid parts function createTextPart(text: string): MessageV2.Part { return { - id: "1", + id: PartID.ascending(), sessionID: SessionID.make("s"), messageID: MessageID.make("m"), type: "text" as const, @@ -16,7 +16,7 @@ function createTextPart(text: string): MessageV2.Part { function createReasoningPart(text: string): MessageV2.Part { return { - id: "1", + id: PartID.ascending(), sessionID: SessionID.make("s"), messageID: MessageID.make("m"), type: "reasoning" as const, @@ -28,7 +28,7 @@ function createReasoningPart(text: string): MessageV2.Part { function createToolPart(tool: string, title: string, status: "completed" | "running" = "completed"): MessageV2.Part { if (status === "completed") { return { - id: "1", + id: PartID.ascending(), sessionID: SessionID.make("s"), messageID: MessageID.make("m"), type: "tool" as const, @@ -45,7 +45,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn } } return { - id: "1", + id: PartID.ascending(), sessionID: SessionID.make("s"), messageID: MessageID.make("m"), type: "tool" as const, @@ -61,7 +61,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn function createStepStartPart(): MessageV2.Part { return { - id: "1", + id: PartID.ascending(), sessionID: SessionID.make("s"), messageID: MessageID.make("m"), type: "step-start" as const, @@ -70,7 +70,7 @@ function createStepStartPart(): MessageV2.Part { function createStepFinishPart(): MessageV2.Part { return { - id: "1", + id: PartID.ascending(), sessionID: SessionID.make("s"), messageID: MessageID.make("m"), type: "step-finish" as const, diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index af1d0018bc..1a7c75c05f 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import { APICallError } from "ai" import { MessageV2 } from "../../src/session/message-v2" import type { Provider } from "../../src/provider/provider" -import { SessionID, MessageID } from "../../src/session/schema" +import { SessionID, MessageID, PartID } from "../../src/session/schema" const sessionID = SessionID.make("session") const model: Provider.Model = { @@ -98,7 +98,7 @@ function assistantInfo( function basePart(messageID: string, id: string) { return { - id, + id: PartID.make(id), sessionID, messageID: MessageID.make(messageID), } diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index 37b3e21771..1a5844bc91 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -6,8 +6,7 @@ import { SessionCompaction } from "../../src/session/compaction" import { MessageV2 } from "../../src/session/message-v2" import { Log } from "../../src/util/log" import { Instance } from "../../src/project/instance" -import { Identifier } from "../../src/id/id" -import { MessageID } from "../../src/session/schema" +import { MessageID, PartID } from "../../src/session/schema" import { tmpdir } from "../fixture/fixture" const projectRoot = path.join(__dirname, "../..") @@ -40,7 +39,7 @@ describe("revert + compact workflow", () => { // Add a text part to the user message await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMsg1.id, sessionID, type: "text", @@ -77,7 +76,7 @@ describe("revert + compact workflow", () => { // Add a text part to the assistant message await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: assistantMsg1.id, sessionID, type: "text", @@ -100,7 +99,7 @@ describe("revert + compact workflow", () => { }) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMsg2.id, sessionID, type: "text", @@ -136,7 +135,7 @@ describe("revert + compact workflow", () => { await Session.updateMessage(assistantMsg2) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: assistantMsg2.id, sessionID, type: "text", @@ -215,7 +214,7 @@ describe("revert + compact workflow", () => { }) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: userMsg.id, sessionID, type: "text", @@ -250,7 +249,7 @@ describe("revert + compact workflow", () => { await Session.updateMessage(assistantMsg) await Session.updatePart({ - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID: assistantMsg.id, sessionID, type: "text", diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index f5512f240e..2332586223 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -5,8 +5,7 @@ import { Bus } from "../../src/bus" import { Log } from "../../src/util/log" import { Instance } from "../../src/project/instance" import { MessageV2 } from "../../src/session/message-v2" -import { Identifier } from "../../src/id/id" -import { MessageID } from "../../src/session/schema" +import { MessageID, PartID } from "../../src/session/schema" const projectRoot = path.join(__dirname, "../..") Log.init({ print: false }) @@ -108,7 +107,7 @@ describe("step-finish token propagation via Bus event", () => { } const partInput = { - id: Identifier.ascending("part"), + id: PartID.ascending(), messageID, sessionID: session.id, type: "step-finish" as const, diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts index f436037158..a714f11473 100644 --- a/packages/opencode/test/storage/json-migration.test.ts +++ b/packages/opencode/test/storage/json-migration.test.ts @@ -11,7 +11,7 @@ import { ProjectTable } from "../../src/project/project.sql" import { ProjectID } from "../../src/project/schema" import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql" import { SessionShareTable } from "../../src/share/share.sql" -import { SessionID, MessageID } from "../../src/session/schema" +import { SessionID, MessageID, PartID } from "../../src/session/schema" // Test fixtures const fixtures = { @@ -259,7 +259,7 @@ describe("JSON to SQLite migration", () => { const parts = db.select().from(PartTable).all() expect(parts.length).toBe(1) - expect(parts[0].id).toBe("prt_testabc123") + expect(parts[0].id).toBe(PartID.make("prt_testabc123")) }) test("migrates legacy parts without ids in body", async () => { @@ -302,7 +302,7 @@ describe("JSON to SQLite migration", () => { const parts = db.select().from(PartTable).all() expect(parts.length).toBe(1) - expect(parts[0].id).toBe("prt_testabc123") + expect(parts[0].id).toBe(PartID.make("prt_testabc123")) expect(parts[0].message_id).toBe(MessageID.make("msg_test789ghi")) expect(parts[0].session_id).toBe(SessionID.make("ses_test456def")) expect(parts[0].data).not.toHaveProperty("id") @@ -374,7 +374,7 @@ describe("JSON to SQLite migration", () => { const db = drizzle({ client: sqlite }) const parts = db.select().from(PartTable).all() expect(parts.length).toBe(1) - expect(parts[0].id).toBe("prt_from_filename") // Uses filename, not JSON id + expect(parts[0].id).toBe(PartID.make("prt_from_filename")) // Uses filename, not JSON id expect(parts[0].message_id).toBe(MessageID.make("msg_realmsgid")) // Uses parent dir, not JSON messageID }) From 83f083ee0d320ab7cd800278cdde3e2eff23ce86 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:41:43 +0000 Subject: [PATCH 114/200] chore: generate --- packages/sdk/openapi.json | 69 ++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 33e4927bf0..ccb6e33220 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -3253,7 +3253,8 @@ "in": "path", "name": "partID", "schema": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "required": true } @@ -3337,7 +3338,8 @@ "in": "path", "name": "partID", "schema": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "required": true } @@ -3634,7 +3636,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -7633,7 +7636,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -7682,7 +7686,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -7727,7 +7732,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -7894,7 +7900,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8089,7 +8096,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8126,7 +8134,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8150,7 +8159,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8210,7 +8220,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8234,7 +8245,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8264,7 +8276,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8307,7 +8320,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8343,7 +8357,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "sessionID": { "type": "string", @@ -8444,7 +8459,8 @@ "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "field": { "type": "string" @@ -8477,7 +8493,8 @@ "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" } }, "required": ["sessionID", "messageID", "partID"] @@ -9207,7 +9224,8 @@ "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "snapshot": { "type": "string" @@ -11296,7 +11314,8 @@ "pattern": "^msg.*" }, "partID": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "snapshot": { "type": "string" @@ -11345,7 +11364,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -11386,7 +11406,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -11411,7 +11432,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", @@ -11446,7 +11468,8 @@ "type": "object", "properties": { "id": { - "type": "string" + "type": "string", + "pattern": "^prt.*" }, "type": { "type": "string", From 547eb7676df427de6b5195005e2a3b0322a72f1c Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:25:09 +1000 Subject: [PATCH 115/200] feat(windows): add arm64 release targets for cli and desktop (#16696) --- .github/workflows/publish.yml | 7 +++++++ .../scripts/finalize-latest-yml.ts | 14 +++++++++++--- packages/desktop-electron/scripts/utils.ts | 7 ++++++- packages/desktop/scripts/finalize-latest-json.ts | 2 ++ packages/desktop/scripts/utils.ts | 5 +++++ packages/opencode/package.json | 1 + packages/opencode/script/build.ts | 4 ++++ packages/opencode/src/file/ripgrep.ts | 1 + 8 files changed, 37 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 079e6d6f09..b425b32a58 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -115,6 +115,9 @@ jobs: target: x86_64-apple-darwin - host: macos-latest target: aarch64-apple-darwin + # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain + - host: windows-2025 + target: aarch64-pc-windows-msvc - host: blacksmith-4vcpu-windows-2025 target: x86_64-pc-windows-msvc - host: blacksmith-4vcpu-ubuntu-2404 @@ -258,6 +261,10 @@ jobs: - host: macos-latest target: aarch64-apple-darwin platform_flag: --mac --arm64 + # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain + - host: "windows-2025" + target: aarch64-pc-windows-msvc + platform_flag: --win --arm64 - host: "blacksmith-4vcpu-windows-2025" target: x86_64-pc-windows-msvc platform_flag: --win diff --git a/packages/desktop-electron/scripts/finalize-latest-yml.ts b/packages/desktop-electron/scripts/finalize-latest-yml.ts index 42ec23b642..aa2ae5c96e 100644 --- a/packages/desktop-electron/scripts/finalize-latest-yml.ts +++ b/packages/desktop-electron/scripts/finalize-latest-yml.ts @@ -78,9 +78,17 @@ async function read(subdir: string, filename: string): Promise<LatestYml | undef const output: Record<string, string> = {} -// Windows: single arch, pass through -const win = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml") -if (win) output["latest.yml"] = serialize(win) +// Windows: merge arm64 + x64 into single file +const winX64 = await read("latest-yml-x86_64-pc-windows-msvc", "latest.yml") +const winArm64 = await read("latest-yml-aarch64-pc-windows-msvc", "latest.yml") +if (winX64 || winArm64) { + const base = winArm64 ?? winX64! + output["latest.yml"] = serialize({ + version: base.version, + files: [...(winArm64?.files ?? []), ...(winX64?.files ?? [])], + releaseDate: base.releaseDate, + }) +} // Linux x64: pass through const linuxX64 = await read("latest-yml-x86_64-unknown-linux-gnu", "latest-linux.yml") diff --git a/packages/desktop-electron/scripts/utils.ts b/packages/desktop-electron/scripts/utils.ts index 4c9af1fc7e..1c0add87d3 100644 --- a/packages/desktop-electron/scripts/utils.ts +++ b/packages/desktop-electron/scripts/utils.ts @@ -19,6 +19,11 @@ export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; ass ocBinary: "opencode-darwin-x64-baseline", assetExt: "zip", }, + { + rustTarget: "aarch64-pc-windows-msvc", + ocBinary: "opencode-windows-arm64", + assetExt: "zip", + }, { rustTarget: "x86_64-pc-windows-msvc", ocBinary: "opencode-windows-x64-baseline", @@ -41,7 +46,7 @@ export const RUST_TARGET = Bun.env.RUST_TARGET function nativeTarget() { const { platform, arch } = process if (platform === "darwin") return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin" - if (platform === "win32") return "x86_64-pc-windows-msvc" + if (platform === "win32") return arch === "arm64" ? "aarch64-pc-windows-msvc" : "x86_64-pc-windows-msvc" if (platform === "linux") return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu" throw new Error(`Unsupported platform: ${platform}/${arch}`) } diff --git a/packages/desktop/scripts/finalize-latest-json.ts b/packages/desktop/scripts/finalize-latest-json.ts index 5445e0640d..a2b95d2c47 100644 --- a/packages/desktop/scripts/finalize-latest-json.ts +++ b/packages/desktop/scripts/finalize-latest-json.ts @@ -101,6 +101,7 @@ const targets = [ { key: "linux-x86_64-rpm", asset: "opencode-desktop-linux-x86_64.rpm" }, { key: "linux-aarch64-deb", asset: "opencode-desktop-linux-arm64.deb" }, { key: "linux-aarch64-rpm", asset: "opencode-desktop-linux-aarch64.rpm" }, + { key: "windows-aarch64-nsis", asset: "opencode-desktop-windows-arm64.exe" }, { key: "windows-x86_64-nsis", asset: "opencode-desktop-windows-x64.exe" }, { key: "darwin-x86_64-app", asset: "opencode-desktop-darwin-x64.app.tar.gz" }, { @@ -129,6 +130,7 @@ const alias = (key: string, source: string) => { alias("linux-x86_64", "linux-x86_64-deb") alias("linux-aarch64", "linux-aarch64-deb") +alias("windows-aarch64", "windows-aarch64-nsis") alias("windows-x86_64", "windows-x86_64-nsis") alias("darwin-x86_64", "darwin-x86_64-app") alias("darwin-aarch64", "darwin-aarch64-app") diff --git a/packages/desktop/scripts/utils.ts b/packages/desktop/scripts/utils.ts index 2629eb466c..06ff25946e 100644 --- a/packages/desktop/scripts/utils.ts +++ b/packages/desktop/scripts/utils.ts @@ -11,6 +11,11 @@ export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; ass ocBinary: "opencode-darwin-x64-baseline", assetExt: "zip", }, + { + rustTarget: "aarch64-pc-windows-msvc", + ocBinary: "opencode-windows-arm64", + assetExt: "zip", + }, { rustTarget: "x86_64-pc-windows-msvc", ocBinary: "opencode-windows-x64-baseline", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index fe991ad8ab..e277977f7a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -36,6 +36,7 @@ "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1", "@standard-schema/spec": "1.0.0", "@tsconfig/bun": "catalog:", diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 4ff610862d..81694aeac1 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -108,6 +108,10 @@ const allTargets: { arch: "x64", avx2: false, }, + { + os: "win32", + arch: "arm64", + }, { os: "win32", arch: "x64", diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index b1068f5d8e..601c82e94f 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -100,6 +100,7 @@ export namespace Ripgrep { }, "x64-darwin": { platform: "x86_64-apple-darwin", extension: "tar.gz" }, "x64-linux": { platform: "x86_64-unknown-linux-musl", extension: "tar.gz" }, + "arm64-win32": { platform: "aarch64-pc-windows-msvc", extension: "zip" }, "x64-win32": { platform: "x86_64-pc-windows-msvc", extension: "zip" }, } as const From b0bca6342ece143e53fb07e2c0d7f75625300185 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:26:05 +0000 Subject: [PATCH 116/200] chore: generate --- bun.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/bun.lock b/bun.lock index 1721ba330b..5e66e3e167 100644 --- a/bun.lock +++ b/bun.lock @@ -392,6 +392,7 @@ "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1", "@standard-schema/spec": "1.0.0", "@tsconfig/bun": "catalog:", From 2a4dedc21075b4913e1c4b575e344e6690a5d9f8 Mon Sep 17 00:00:00 2001 From: Kit Langton <kit.langton@gmail.com> Date: Wed, 11 Mar 2026 21:49:57 -0400 Subject: [PATCH 117/200] feat(id): brand PermissionID, PtyID, QuestionID, and ToolID (#17042) --- packages/opencode/src/account/schema.ts | 4 ++-- packages/opencode/src/control-plane/schema.ts | 4 ++-- packages/opencode/src/permission/index.ts | 8 ++++---- packages/opencode/src/permission/next.ts | 10 +++++----- packages/opencode/src/permission/schema.ts | 17 +++++++++++++++++ packages/opencode/src/project/schema.ts | 2 +- packages/opencode/src/pty/index.ts | 12 ++++++------ packages/opencode/src/pty/schema.ts | 17 +++++++++++++++++ packages/opencode/src/question/index.ts | 10 +++++----- packages/opencode/src/question/schema.ts | 17 +++++++++++++++++ .../opencode/src/server/routes/permission.ts | 3 ++- packages/opencode/src/server/routes/pty.ts | 11 ++++++----- .../opencode/src/server/routes/question.ts | 5 +++-- .../opencode/src/server/routes/session.ts | 3 ++- packages/opencode/src/session/schema.ts | 12 ++++++------ packages/opencode/src/tool/schema.ts | 17 +++++++++++++++++ packages/opencode/src/tool/truncation.ts | 3 ++- .../opencode/test/permission/next.test.ts | 19 ++++++++++--------- .../opencode/test/question/question.test.ts | 5 +++-- 19 files changed, 127 insertions(+), 52 deletions(-) create mode 100644 packages/opencode/src/permission/schema.ts create mode 100644 packages/opencode/src/pty/schema.ts create mode 100644 packages/opencode/src/question/schema.ts create mode 100644 packages/opencode/src/tool/schema.ts diff --git a/packages/opencode/src/account/schema.ts b/packages/opencode/src/account/schema.ts index 6b62484ab6..9b31c4ba65 100644 --- a/packages/opencode/src/account/schema.ts +++ b/packages/opencode/src/account/schema.ts @@ -3,13 +3,13 @@ import { Schema } from "effect" import { withStatics } from "@/util/schema" export const AccountID = Schema.String.pipe( - Schema.brand("AccountId"), + Schema.brand("AccountID"), withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })), ) export type AccountID = Schema.Schema.Type<typeof AccountID> export const OrgID = Schema.String.pipe( - Schema.brand("OrgId"), + Schema.brand("OrgID"), withStatics((s) => ({ make: (id: string) => s.makeUnsafe(id) })), ) export type OrgID = Schema.Schema.Type<typeof OrgID> diff --git a/packages/opencode/src/control-plane/schema.ts b/packages/opencode/src/control-plane/schema.ts index 00a9f8b56a..7618f46ad4 100644 --- a/packages/opencode/src/control-plane/schema.ts +++ b/packages/opencode/src/control-plane/schema.ts @@ -4,7 +4,7 @@ import z from "zod" import { withStatics } from "@/util/schema" import { Identifier } from "@/id/id" -const workspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceId")) +const workspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceID")) export type WorkspaceID = typeof workspaceIdSchema.Type @@ -12,6 +12,6 @@ export const WorkspaceID = workspaceIdSchema.pipe( withStatics((schema: typeof workspaceIdSchema) => ({ make: (id: string) => schema.makeUnsafe(id), ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("workspace", id)), - zod: z.string().startsWith("wrk").pipe(z.custom<WorkspaceID>()), + zod: Identifier.schema("workspace").pipe(z.custom<WorkspaceID>()), })), ) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 8a2ba64e11..d8283a1fae 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -3,10 +3,10 @@ import { Bus } from "@/bus" import { SessionID, MessageID } from "@/session/schema" import z from "zod" import { Log } from "../util/log" -import { Identifier } from "../id/id" import { Plugin } from "../plugin" import { Instance } from "../project/instance" import { Wildcard } from "../util/wildcard" +import { PermissionID } from "./schema" export namespace Permission { const log = Log.create({ service: "permission" }) @@ -22,7 +22,7 @@ export namespace Permission { export const Info = z .object({ - id: z.string(), + id: PermissionID.zod, type: z.string(), pattern: z.union([z.string(), z.array(z.string())]).optional(), sessionID: SessionID.zod, @@ -45,7 +45,7 @@ export namespace Permission { "permission.replied", z.object({ sessionID: SessionID.zod, - permissionID: z.string(), + permissionID: PermissionID.zod, response: z.string(), }), ), @@ -118,7 +118,7 @@ export namespace Permission { const keys = toKeys(input.pattern, input.type) if (covered(keys, approvedForSession)) return const info: Info = { - id: Identifier.ascending("permission"), + id: PermissionID.ascending(), type: input.type, pattern: input.pattern, sessionID: input.sessionID, diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 2d665a832a..9b8910144b 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -1,8 +1,8 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { Config } from "@/config/config" -import { Identifier } from "@/id/id" import { SessionID, MessageID } from "@/session/schema" +import { PermissionID } from "./schema" import { Instance } from "@/project/instance" import { Database, eq } from "@/storage/db" import { PermissionTable } from "@/session/session.sql" @@ -69,7 +69,7 @@ export namespace PermissionNext { export const Request = z .object({ - id: Identifier.schema("permission"), + id: PermissionID.zod, sessionID: SessionID.zod, permission: z.string(), patterns: z.string().array(), @@ -102,7 +102,7 @@ export namespace PermissionNext { "permission.replied", z.object({ sessionID: SessionID.zod, - requestID: z.string(), + requestID: PermissionID.zod, reply: Reply, }), ), @@ -143,7 +143,7 @@ export namespace PermissionNext { if (rule.action === "deny") throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) if (rule.action === "ask") { - const id = input.id ?? Identifier.ascending("permission") + const id = input.id ?? PermissionID.ascending() return new Promise<void>((resolve, reject) => { const info: Request = { id, @@ -164,7 +164,7 @@ export namespace PermissionNext { export const reply = fn( z.object({ - requestID: Identifier.schema("permission"), + requestID: PermissionID.zod, reply: Reply, message: z.string().optional(), }), diff --git a/packages/opencode/src/permission/schema.ts b/packages/opencode/src/permission/schema.ts new file mode 100644 index 0000000000..c3242b714a --- /dev/null +++ b/packages/opencode/src/permission/schema.ts @@ -0,0 +1,17 @@ +import { Schema } from "effect" +import z from "zod" + +import { Identifier } from "@/id/id" +import { withStatics } from "@/util/schema" + +const permissionIdSchema = Schema.String.pipe(Schema.brand("PermissionID")) + +export type PermissionID = typeof permissionIdSchema.Type + +export const PermissionID = permissionIdSchema.pipe( + withStatics((schema: typeof permissionIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("permission", id)), + zod: Identifier.schema("permission").pipe(z.custom<PermissionID>()), + })), +) diff --git a/packages/opencode/src/project/schema.ts b/packages/opencode/src/project/schema.ts index adaa9a8314..e904ff5a84 100644 --- a/packages/opencode/src/project/schema.ts +++ b/packages/opencode/src/project/schema.ts @@ -3,7 +3,7 @@ import z from "zod" import { withStatics } from "@/util/schema" -const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectId")) +const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectID")) export type ProjectID = typeof projectIdSchema.Type diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 1c27d5b847..077a9dca56 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -2,12 +2,12 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { type IPty } from "bun-pty" import z from "zod" -import { Identifier } from "../id/id" import { Log } from "../util/log" import { Instance } from "../project/instance" import { lazy } from "@opencode-ai/util/lazy" import { Shell } from "@/shell/shell" import { Plugin } from "@/plugin" +import { PtyID } from "./schema" export namespace Pty { const log = Log.create({ service: "pty" }) @@ -40,7 +40,7 @@ export namespace Pty { export const Info = z .object({ - id: Identifier.schema("pty"), + id: PtyID.zod, title: z.string(), command: z.string(), args: z.array(z.string()), @@ -77,8 +77,8 @@ export namespace Pty { export const Event = { Created: BusEvent.define("pty.created", z.object({ info: Info })), Updated: BusEvent.define("pty.updated", z.object({ info: Info })), - Exited: BusEvent.define("pty.exited", z.object({ id: Identifier.schema("pty"), exitCode: z.number() })), - Deleted: BusEvent.define("pty.deleted", z.object({ id: Identifier.schema("pty") })), + Exited: BusEvent.define("pty.exited", z.object({ id: PtyID.zod, exitCode: z.number() })), + Deleted: BusEvent.define("pty.deleted", z.object({ id: PtyID.zod })), } interface ActiveSession { @@ -118,7 +118,7 @@ export namespace Pty { } export async function create(input: CreateInput) { - const id = Identifier.create("pty", false) + const id = PtyID.ascending() const command = input.command || Shell.preferred() const args = input.args || [] if (command.endsWith("sh")) { @@ -234,7 +234,7 @@ export namespace Pty { } } session.subscribers.clear() - Bus.publish(Event.Deleted, { id }) + Bus.publish(Event.Deleted, { id: session.info.id }) } export function resize(id: string, cols: number, rows: number) { diff --git a/packages/opencode/src/pty/schema.ts b/packages/opencode/src/pty/schema.ts new file mode 100644 index 0000000000..47b3196f0b --- /dev/null +++ b/packages/opencode/src/pty/schema.ts @@ -0,0 +1,17 @@ +import { Schema } from "effect" +import z from "zod" + +import { Identifier } from "@/id/id" +import { withStatics } from "@/util/schema" + +const ptyIdSchema = Schema.String.pipe(Schema.brand("PtyID")) + +export type PtyID = typeof ptyIdSchema.Type + +export const PtyID = ptyIdSchema.pipe( + withStatics((schema: typeof ptyIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("pty", id)), + zod: Identifier.schema("pty").pipe(z.custom<PtyID>()), + })), +) diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index 2142fe183f..0e145a0407 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -1,10 +1,10 @@ import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" -import { Identifier } from "@/id/id" import { SessionID, MessageID } from "@/session/schema" import { Instance } from "@/project/instance" import { Log } from "@/util/log" import z from "zod" +import { QuestionID } from "./schema" export namespace Question { const log = Log.create({ service: "question" }) @@ -34,7 +34,7 @@ export namespace Question { export const Request = z .object({ - id: Identifier.schema("question"), + id: QuestionID.zod, sessionID: SessionID.zod, questions: z.array(Info).describe("Questions to ask"), tool: z @@ -67,7 +67,7 @@ export namespace Question { "question.replied", z.object({ sessionID: SessionID.zod, - requestID: z.string(), + requestID: QuestionID.zod, answers: z.array(Answer), }), ), @@ -75,7 +75,7 @@ export namespace Question { "question.rejected", z.object({ sessionID: SessionID.zod, - requestID: z.string(), + requestID: QuestionID.zod, }), ), } @@ -101,7 +101,7 @@ export namespace Question { tool?: { messageID: MessageID; callID: string } }): Promise<Answer[]> { const s = await state() - const id = Identifier.ascending("question") + const id = QuestionID.ascending() log.info("asking", { id, questions: input.questions.length }) diff --git a/packages/opencode/src/question/schema.ts b/packages/opencode/src/question/schema.ts new file mode 100644 index 0000000000..65e9ad07cb --- /dev/null +++ b/packages/opencode/src/question/schema.ts @@ -0,0 +1,17 @@ +import { Schema } from "effect" +import z from "zod" + +import { Identifier } from "@/id/id" +import { withStatics } from "@/util/schema" + +const questionIdSchema = Schema.String.pipe(Schema.brand("QuestionID")) + +export type QuestionID = typeof questionIdSchema.Type + +export const QuestionID = questionIdSchema.pipe( + withStatics((schema: typeof questionIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("question", id)), + zod: Identifier.schema("question").pipe(z.custom<QuestionID>()), + })), +) diff --git a/packages/opencode/src/server/routes/permission.ts b/packages/opencode/src/server/routes/permission.ts index 8fc2d594d7..6d86703c66 100644 --- a/packages/opencode/src/server/routes/permission.ts +++ b/packages/opencode/src/server/routes/permission.ts @@ -2,6 +2,7 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" import { PermissionNext } from "@/permission/next" +import { PermissionID } from "@/permission/schema" import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -28,7 +29,7 @@ export const PermissionRoutes = lazy(() => validator( "param", z.object({ - requestID: z.string(), + requestID: PermissionID.zod, }), ), validator("json", z.object({ reply: PermissionNext.Reply, message: z.string().optional() })), diff --git a/packages/opencode/src/server/routes/pty.ts b/packages/opencode/src/server/routes/pty.ts index 368c9612bf..0f9151ba4e 100644 --- a/packages/opencode/src/server/routes/pty.ts +++ b/packages/opencode/src/server/routes/pty.ts @@ -3,6 +3,7 @@ import { describeRoute, validator, resolver } from "hono-openapi" import { upgradeWebSocket } from "hono/bun" import z from "zod" import { Pty } from "@/pty" +import { PtyID } from "@/pty/schema" import { NotFoundError } from "../../storage/db" import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -72,7 +73,7 @@ export const PtyRoutes = lazy(() => ...errors(404), }, }), - validator("param", z.object({ ptyID: z.string() })), + validator("param", z.object({ ptyID: PtyID.zod })), async (c) => { const info = Pty.get(c.req.valid("param").ptyID) if (!info) { @@ -99,7 +100,7 @@ export const PtyRoutes = lazy(() => ...errors(400), }, }), - validator("param", z.object({ ptyID: z.string() })), + validator("param", z.object({ ptyID: PtyID.zod })), validator("json", Pty.UpdateInput), async (c) => { const info = await Pty.update(c.req.valid("param").ptyID, c.req.valid("json")) @@ -124,7 +125,7 @@ export const PtyRoutes = lazy(() => ...errors(404), }, }), - validator("param", z.object({ ptyID: z.string() })), + validator("param", z.object({ ptyID: PtyID.zod })), async (c) => { await Pty.remove(c.req.valid("param").ptyID) return c.json(true) @@ -148,9 +149,9 @@ export const PtyRoutes = lazy(() => ...errors(404), }, }), - validator("param", z.object({ ptyID: z.string() })), + validator("param", z.object({ ptyID: PtyID.zod })), upgradeWebSocket((c) => { - const id = c.req.param("ptyID") + const id = PtyID.zod.parse(c.req.param("ptyID")) const cursor = (() => { const value = c.req.query("cursor") if (!value) return diff --git a/packages/opencode/src/server/routes/question.ts b/packages/opencode/src/server/routes/question.ts index eab675e816..3fff895fa6 100644 --- a/packages/opencode/src/server/routes/question.ts +++ b/packages/opencode/src/server/routes/question.ts @@ -1,6 +1,7 @@ import { Hono } from "hono" import { describeRoute, validator } from "hono-openapi" import { resolver } from "hono-openapi" +import { QuestionID } from "@/question/schema" import { Question } from "../../question" import z from "zod" import { errors } from "../error" @@ -51,7 +52,7 @@ export const QuestionRoutes = lazy(() => validator( "param", z.object({ - requestID: z.string(), + requestID: QuestionID.zod, }), ), validator("json", Question.Reply), @@ -86,7 +87,7 @@ export const QuestionRoutes = lazy(() => validator( "param", z.object({ - requestID: z.string(), + requestID: QuestionID.zod, }), ), async (c) => { diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 39dd1b580e..1c7cba28dc 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -15,6 +15,7 @@ import { Agent } from "../../agent/agent" import { Snapshot } from "@/snapshot" import { Log } from "../../util/log" import { PermissionNext } from "@/permission/next" +import { PermissionID } from "@/permission/schema" import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -957,7 +958,7 @@ export const SessionRoutes = lazy(() => "param", z.object({ sessionID: SessionID.zod, - permissionID: z.string(), + permissionID: PermissionID.zod, }), ), validator("json", z.object({ response: PermissionNext.Reply })), diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts index 6504a1b393..b37fefae69 100644 --- a/packages/opencode/src/session/schema.ts +++ b/packages/opencode/src/session/schema.ts @@ -4,7 +4,7 @@ import z from "zod" import { withStatics } from "@/util/schema" import { Identifier } from "@/id/id" -const sessionIdSchema = Schema.String.pipe(Schema.brand("SessionId")) +const sessionIdSchema = Schema.String.pipe(Schema.brand("SessionID")) export type SessionID = typeof sessionIdSchema.Type @@ -12,11 +12,11 @@ export const SessionID = sessionIdSchema.pipe( withStatics((schema: typeof sessionIdSchema) => ({ make: (id: string) => schema.makeUnsafe(id), descending: (id?: string) => schema.makeUnsafe(Identifier.descending("session", id)), - zod: z.string().startsWith("ses").pipe(z.custom<SessionID>()), + zod: Identifier.schema("session").pipe(z.custom<SessionID>()), })), ) -const messageIdSchema = Schema.String.pipe(Schema.brand("MessageId")) +const messageIdSchema = Schema.String.pipe(Schema.brand("MessageID")) export type MessageID = typeof messageIdSchema.Type @@ -24,11 +24,11 @@ export const MessageID = messageIdSchema.pipe( withStatics((schema: typeof messageIdSchema) => ({ make: (id: string) => schema.makeUnsafe(id), ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("message", id)), - zod: z.string().startsWith("msg").pipe(z.custom<MessageID>()), + zod: Identifier.schema("message").pipe(z.custom<MessageID>()), })), ) -const partIdSchema = Schema.String.pipe(Schema.brand("PartId")) +const partIdSchema = Schema.String.pipe(Schema.brand("PartID")) export type PartID = typeof partIdSchema.Type @@ -36,6 +36,6 @@ export const PartID = partIdSchema.pipe( withStatics((schema: typeof partIdSchema) => ({ make: (id: string) => schema.makeUnsafe(id), ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("part", id)), - zod: z.string().startsWith("prt").pipe(z.custom<PartID>()), + zod: Identifier.schema("part").pipe(z.custom<PartID>()), })), ) diff --git a/packages/opencode/src/tool/schema.ts b/packages/opencode/src/tool/schema.ts new file mode 100644 index 0000000000..93f0f9a71f --- /dev/null +++ b/packages/opencode/src/tool/schema.ts @@ -0,0 +1,17 @@ +import { Schema } from "effect" +import z from "zod" + +import { Identifier } from "@/id/id" +import { withStatics } from "@/util/schema" + +const toolIdSchema = Schema.String.pipe(Schema.brand("ToolID")) + +export type ToolID = typeof toolIdSchema.Type + +export const ToolID = toolIdSchema.pipe( + withStatics((schema: typeof toolIdSchema) => ({ + make: (id: string) => schema.makeUnsafe(id), + ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("tool", id)), + zod: Identifier.schema("tool").pipe(z.custom<ToolID>()), + })), +) diff --git a/packages/opencode/src/tool/truncation.ts b/packages/opencode/src/tool/truncation.ts index 58b0cc13d9..7c6a362a37 100644 --- a/packages/opencode/src/tool/truncation.ts +++ b/packages/opencode/src/tool/truncation.ts @@ -7,6 +7,7 @@ import type { Agent } from "../agent/agent" import { Scheduler } from "../scheduler" import { Filesystem } from "../util/filesystem" import { Glob } from "../util/glob" +import { ToolID } from "./schema" export namespace Truncate { export const MAX_LINES = 2000 @@ -90,7 +91,7 @@ export namespace Truncate { const unit = hitBytes ? "bytes" : "lines" const preview = out.join("\n") - const id = Identifier.ascending("tool") + const id = ToolID.ascending() const filepath = path.join(DIR, id) await Filesystem.write(filepath, text) diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index b66b40dc09..7fd0818991 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -1,6 +1,7 @@ import { test, expect } from "bun:test" import os from "os" import { PermissionNext } from "../../src/permission/next" +import { PermissionID } from "../../src/permission/schema" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import { SessionID } from "../../src/session/schema" @@ -522,7 +523,7 @@ test("reply - once resolves the pending ask", async () => { directory: tmp.path, fn: async () => { const askPromise = PermissionNext.ask({ - id: "permission_test1", + id: PermissionID.make("per_test1"), sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], @@ -532,7 +533,7 @@ test("reply - once resolves the pending ask", async () => { }) await PermissionNext.reply({ - requestID: "permission_test1", + requestID: PermissionID.make("per_test1"), reply: "once", }) @@ -547,7 +548,7 @@ test("reply - reject throws RejectedError", async () => { directory: tmp.path, fn: async () => { const askPromise = PermissionNext.ask({ - id: "permission_test2", + id: PermissionID.make("per_test2"), sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], @@ -557,7 +558,7 @@ test("reply - reject throws RejectedError", async () => { }) await PermissionNext.reply({ - requestID: "permission_test2", + requestID: PermissionID.make("per_test2"), reply: "reject", }) @@ -572,7 +573,7 @@ test("reply - always persists approval and resolves", async () => { directory: tmp.path, fn: async () => { const askPromise = PermissionNext.ask({ - id: "permission_test3", + id: PermissionID.make("per_test3"), sessionID: SessionID.make("session_test"), permission: "bash", patterns: ["ls"], @@ -582,7 +583,7 @@ test("reply - always persists approval and resolves", async () => { }) await PermissionNext.reply({ - requestID: "permission_test3", + requestID: PermissionID.make("per_test3"), reply: "always", }) @@ -613,7 +614,7 @@ test("reply - reject cancels all pending for same session", async () => { directory: tmp.path, fn: async () => { const askPromise1 = PermissionNext.ask({ - id: "permission_test4a", + id: PermissionID.make("per_test4a"), sessionID: SessionID.make("session_same"), permission: "bash", patterns: ["ls"], @@ -623,7 +624,7 @@ test("reply - reject cancels all pending for same session", async () => { }) const askPromise2 = PermissionNext.ask({ - id: "permission_test4b", + id: PermissionID.make("per_test4b"), sessionID: SessionID.make("session_same"), permission: "edit", patterns: ["foo.ts"], @@ -638,7 +639,7 @@ test("reply - reject cancels all pending for same session", async () => { // Reject the first one await PermissionNext.reply({ - requestID: "permission_test4a", + requestID: PermissionID.make("per_test4a"), reply: "reject", }) diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index c1c68c7472..f00afb09fd 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.test.ts @@ -1,6 +1,7 @@ import { test, expect } from "bun:test" import { Question } from "../../src/question" import { Instance } from "../../src/project/instance" +import { QuestionID } from "../../src/question/schema" import { tmpdir } from "../fixture/fixture" import { SessionID } from "../../src/session/schema" @@ -131,7 +132,7 @@ test("reply - does nothing for unknown requestID", async () => { directory: tmp.path, fn: async () => { await Question.reply({ - requestID: "que_unknown", + requestID: QuestionID.make("que_unknown"), answers: [["Option 1"]], }) // Should not throw @@ -204,7 +205,7 @@ test("reject - does nothing for unknown requestID", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - await Question.reject("que_unknown") + await Question.reject(QuestionID.make("que_unknown")) // Should not throw }, }) From 2c825c32234753c1a3c4a165092ddaef904c7f62 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 01:50:44 +0000 Subject: [PATCH 118/200] chore: generate --- packages/sdk/openapi.json | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index ccb6e33220..a5e10f0d73 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -637,7 +637,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -694,7 +695,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -777,7 +779,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -836,7 +839,8 @@ "in": "path", "name": "ptyID", "schema": { - "type": "string" + "type": "string", + "pattern": "^pty.*" }, "required": true } @@ -3966,7 +3970,8 @@ "in": "path", "name": "permissionID", "schema": { - "type": "string" + "type": "string", + "pattern": "^per.*" }, "required": true } @@ -4052,7 +4057,8 @@ "in": "path", "name": "requestID", "schema": { - "type": "string" + "type": "string", + "pattern": "^per.*" }, "required": true } @@ -4228,7 +4234,8 @@ "in": "path", "name": "requestID", "schema": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "required": true } @@ -4316,7 +4323,8 @@ "in": "path", "name": "requestID", "schema": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "required": true } @@ -8579,7 +8587,8 @@ "pattern": "^ses.*" }, "requestID": { - "type": "string" + "type": "string", + "pattern": "^per.*" }, "reply": { "type": "string", @@ -8788,7 +8797,8 @@ "pattern": "^ses.*" }, "requestID": { - "type": "string" + "type": "string", + "pattern": "^que.*" }, "answers": { "type": "array", @@ -8817,7 +8827,8 @@ "pattern": "^ses.*" }, "requestID": { - "type": "string" + "type": "string", + "pattern": "^que.*" } }, "required": ["sessionID", "requestID"] From 1d9c83b576c51623f81014c94ce68c85734434fa Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 12 Mar 2026 12:03:38 +1000 Subject: [PATCH 119/200] fix(e2e): re-focus prompt after terminal opens in slash-terminal test (#17113) --- .../app/e2e/prompt/prompt-slash-terminal.spec.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts index bf9f96b47f..793b34ff81 100644 --- a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts +++ b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts @@ -6,16 +6,24 @@ test("/terminal toggles the terminal panel", async ({ page, gotoSession }) => { const prompt = page.locator(promptSelector) const terminal = page.locator(terminalSelector) + const slash = page.locator('[data-slash-id="terminal.toggle"]').first() await expect(terminal).not.toBeVisible() await prompt.fill("/terminal") - await expect(page.locator('[data-slash-id="terminal.toggle"]').first()).toBeVisible() + await expect(slash).toBeVisible() await page.keyboard.press("Enter") await expect(terminal).toBeVisible() - await prompt.fill("/terminal") - await expect(page.locator('[data-slash-id="terminal.toggle"]').first()).toBeVisible() + // Terminal panel retries focus (immediate, RAF, 120ms, 240ms) after opening, + // which can steal focus from the prompt and prevent fill() from triggering + // the slash popover. Re-attempt click+fill until all retries are exhausted + // and the popover appears. + await expect.poll(async () => { + await prompt.click().catch(() => false) + await prompt.fill("/terminal").catch(() => false) + return slash.isVisible().catch(() => false) + }, { timeout: 10_000 }).toBe(true) await page.keyboard.press("Enter") await expect(terminal).not.toBeVisible() }) From 7b0def4b81b2b029a033ac67f64a9c82a3ebec64 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 02:04:26 +0000 Subject: [PATCH 120/200] chore: generate --- .../app/e2e/prompt/prompt-slash-terminal.spec.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts index 793b34ff81..fa884b752d 100644 --- a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts +++ b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts @@ -19,11 +19,16 @@ test("/terminal toggles the terminal panel", async ({ page, gotoSession }) => { // which can steal focus from the prompt and prevent fill() from triggering // the slash popover. Re-attempt click+fill until all retries are exhausted // and the popover appears. - await expect.poll(async () => { - await prompt.click().catch(() => false) - await prompt.fill("/terminal").catch(() => false) - return slash.isVisible().catch(() => false) - }, { timeout: 10_000 }).toBe(true) + await expect + .poll( + async () => { + await prompt.click().catch(() => false) + await prompt.fill("/terminal").catch(() => false) + return slash.isVisible().catch(() => false) + }, + { timeout: 10_000 }, + ) + .toBe(true) await page.keyboard.press("Enter") await expect(terminal).not.toBeVisible() }) From fafbc29316e7240b348db9e91e7a8e97cd803aef Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:19:28 +1000 Subject: [PATCH 121/200] fix(ci): use dynamic bun cache path for cross-platform support (#17120) --- .github/actions/setup-bun/action.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 3f06da5197..f53f20fcdb 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -3,14 +3,6 @@ description: "Setup Bun with caching and install dependencies" runs: using: "composite" steps: - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }} - restore-keys: | - ${{ runner.os }}-bun- - - name: Get baseline download URL id: bun-url shell: bash @@ -31,6 +23,19 @@ runs: bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }} bun-download-url: ${{ steps.bun-url.outputs.url }} + - name: Get cache directory + id: cache + shell: bash + run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT" + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.cache.outputs.dir }} + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + - name: Install setuptools for distutils compatibility run: python3 -m pip install setuptools || pip install setuptools || true shell: bash From 9fb12a906eb8b1ef5f346623d11bd86689f926b1 Mon Sep 17 00:00:00 2001 From: Dax Raad <d@ironbay.co> Date: Wed, 11 Mar 2026 23:24:26 -0400 Subject: [PATCH 122/200] core: remove external sourcemap generation to reduce build artifacts --- packages/opencode/script/build.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 4ff610862d..8d84b206d9 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -174,7 +174,6 @@ for (const item of targets) { conditions: ["browser"], tsconfig: "./tsconfig.json", plugins: [solidPlugin], - sourcemap: "external", compile: { autoloadBunfig: false, autoloadDotenv: false, From db7bafe9177b40b22060867838d17d53cf29d81b Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:55:16 +1000 Subject: [PATCH 123/200] fix(app): guard comment accessor in message timeline (#17126) --- .../src/pages/session/message-timeline.tsx | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index cb9c61918a..ab919b70e5 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -778,31 +778,35 @@ export function MessageTimeline(props: { <div class="flex w-max min-w-full justify-end gap-2"> <Index each={comments()}> {(commentAccessor: () => MessageComment) => { - const comment = createMemo(() => commentAccessor()) - return ( - <div class="shrink-0 max-w-[260px] rounded-[6px] border border-border-weak-base bg-background-stronger px-2.5 py-2"> - <div class="flex items-center gap-1.5 min-w-0 text-11-medium text-text-strong"> - <FileIcon - node={{ path: comment().path, type: "file" }} - class="size-3.5 shrink-0" - /> - <span class="truncate">{getFilename(comment().path)}</span> - <Show when={comment().selection}> - {(selection) => ( - <span class="shrink-0 text-text-weak"> - {selection().startLine === selection().endLine - ? `:${selection().startLine}` - : `:${selection().startLine}-${selection().endLine}`} - </span> - )} - </Show> - </div> - <div class="pt-1 text-12-regular text-text-strong whitespace-pre-wrap break-words"> - {comment().comment} - </div> - </div> - ) - }} + const comment = createMemo(() => commentAccessor()) + return ( + <Show when={comment()}> + {(c) => ( + <div class="shrink-0 max-w-[260px] rounded-[6px] border border-border-weak-base bg-background-stronger px-2.5 py-2"> + <div class="flex items-center gap-1.5 min-w-0 text-11-medium text-text-strong"> + <FileIcon + node={{ path: c().path, type: "file" }} + class="size-3.5 shrink-0" + /> + <span class="truncate">{getFilename(c().path)}</span> + <Show when={c().selection}> + {(selection) => ( + <span class="shrink-0 text-text-weak"> + {selection().startLine === selection().endLine + ? `:${selection().startLine}` + : `:${selection().startLine}-${selection().endLine}`} + </span> + )} + </Show> + </div> + <div class="pt-1 text-12-regular text-text-strong whitespace-pre-wrap break-words"> + {c().comment} + </div> + </div> + )} + </Show> + ) + }} </Index> </div> </div> From 1d7fcd40b45548e20b43e29cd16b83899c8f6eb1 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <opencode-agent[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 03:56:04 +0000 Subject: [PATCH 124/200] chore: generate --- .../src/pages/session/message-timeline.tsx | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index ab919b70e5..e64f5a7fd6 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -778,35 +778,35 @@ export function MessageTimeline(props: { <div class="flex w-max min-w-full justify-end gap-2"> <Index each={comments()}> {(commentAccessor: () => MessageComment) => { - const comment = createMemo(() => commentAccessor()) - return ( - <Show when={comment()}> - {(c) => ( - <div class="shrink-0 max-w-[260px] rounded-[6px] border border-border-weak-base bg-background-stronger px-2.5 py-2"> - <div class="flex items-center gap-1.5 min-w-0 text-11-medium text-text-strong"> - <FileIcon - node={{ path: c().path, type: "file" }} - class="size-3.5 shrink-0" - /> - <span class="truncate">{getFilename(c().path)}</span> - <Show when={c().selection}> - {(selection) => ( - <span class="shrink-0 text-text-weak"> - {selection().startLine === selection().endLine - ? `:${selection().startLine}` - : `:${selection().startLine}-${selection().endLine}`} - </span> - )} - </Show> - </div> - <div class="pt-1 text-12-regular text-text-strong whitespace-pre-wrap break-words"> - {c().comment} - </div> - </div> - )} - </Show> - ) - }} + const comment = createMemo(() => commentAccessor()) + return ( + <Show when={comment()}> + {(c) => ( + <div class="shrink-0 max-w-[260px] rounded-[6px] border border-border-weak-base bg-background-stronger px-2.5 py-2"> + <div class="flex items-center gap-1.5 min-w-0 text-11-medium text-text-strong"> + <FileIcon + node={{ path: c().path, type: "file" }} + class="size-3.5 shrink-0" + /> + <span class="truncate">{getFilename(c().path)}</span> + <Show when={c().selection}> + {(selection) => ( + <span class="shrink-0 text-text-weak"> + {selection().startLine === selection().endLine + ? `:${selection().startLine}` + : `:${selection().startLine}-${selection().endLine}`} + </span> + )} + </Show> + </div> + <div class="pt-1 text-12-regular text-text-strong whitespace-pre-wrap break-words"> + {c().comment} + </div> + </div> + )} + </Show> + ) + }} </Index> </div> </div> From 54e7baa6cfff86627e5555842560b4a20e4be424 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 12 Mar 2026 14:19:44 +1000 Subject: [PATCH 125/200] fix(desktop-electron): fix resource loading under file:// protocol (#17125) --- .../desktop-electron/electron.vite.config.ts | 2 +- .../src/renderer/html.test.ts | 62 +++++++++++++++++++ .../desktop-electron/src/renderer/index.html | 17 +++-- .../src/renderer/loading.html | 17 +++-- packages/desktop-electron/tsconfig.json | 3 +- packages/ui/src/components/font.tsx | 7 ++- 6 files changed, 86 insertions(+), 22 deletions(-) create mode 100644 packages/desktop-electron/src/renderer/html.test.ts diff --git a/packages/desktop-electron/electron.vite.config.ts b/packages/desktop-electron/electron.vite.config.ts index 80c1d6b704..6903d5ed20 100644 --- a/packages/desktop-electron/electron.vite.config.ts +++ b/packages/desktop-electron/electron.vite.config.ts @@ -27,7 +27,7 @@ export default defineConfig({ }, renderer: { plugins: [appPlugin], - publicDir: "../app/public", + publicDir: "../../../app/public", root: "src/renderer", build: { rollupOptions: { diff --git a/packages/desktop-electron/src/renderer/html.test.ts b/packages/desktop-electron/src/renderer/html.test.ts new file mode 100644 index 0000000000..bd8281c2fb --- /dev/null +++ b/packages/desktop-electron/src/renderer/html.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, test } from "bun:test" +import { join, dirname, resolve } from "node:path" +import { existsSync } from "node:fs" +import { fileURLToPath } from "node:url" + +const dir = dirname(fileURLToPath(import.meta.url)) +const root = resolve(dir, "../..") + +const html = async (name: string) => Bun.file(join(dir, name)).text() + +/** + * Electron loads renderer HTML via `win.loadFile()` which uses the `file://` + * protocol. Absolute paths like `src="/foo.js"` resolve to the filesystem root + * (e.g. `file:///C:/foo.js` on Windows) instead of relative to the app bundle. + * + * All local resource references must use relative paths (`./`). + */ +describe("electron renderer html", () => { + for (const name of ["index.html", "loading.html"]) { + describe(name, () => { + test("script src attributes use relative paths", async () => { + const content = await html(name) + const srcs = [...content.matchAll(/\bsrc=["']([^"']+)["']/g)].map((m) => m[1]) + for (const src of srcs) { + expect(src).not.toMatch(/^\/[^/]/) + } + }) + + test("link href attributes use relative paths", async () => { + const content = await html(name) + const hrefs = [...content.matchAll(/<link[^>]+href=["']([^"']+)["']/g)].map((m) => m[1]) + for (const href of hrefs) { + expect(href).not.toMatch(/^\/[^/]/) + } + }) + + test("no web manifest link (not applicable in Electron)", async () => { + const content = await html(name) + expect(content).not.toContain('rel="manifest"') + }) + }) + } +}) + +/** + * Vite resolves `publicDir` relative to `root`, not the config file. + * This test reads the actual values from electron.vite.config.ts to catch + * regressions where the publicDir path no longer resolves correctly + * after the renderer root is accounted for. + */ +describe("electron vite publicDir", () => { + test("configured publicDir resolves to a directory with oc-theme-preload.js", async () => { + const config = await Bun.file(join(root, "electron.vite.config.ts")).text() + const pub = config.match(/publicDir:\s*["']([^"']+)["']/) + const rendererRoot = config.match(/root:\s*["']([^"']+)["']/) + expect(pub).not.toBeNull() + expect(rendererRoot).not.toBeNull() + const resolved = resolve(root, rendererRoot![1], pub![1]) + expect(existsSync(resolved)).toBe(true) + expect(existsSync(join(resolved, "oc-theme-preload.js"))).toBe(true) + }) +}) diff --git a/packages/desktop-electron/src/renderer/index.html b/packages/desktop-electron/src/renderer/index.html index 1756408196..dd8675ee6b 100644 --- a/packages/desktop-electron/src/renderer/index.html +++ b/packages/desktop-electron/src/renderer/index.html @@ -4,20 +4,19 @@ <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>OpenCode - - - - - + + + + - - - + + +
- + diff --git a/packages/desktop-electron/src/renderer/loading.html b/packages/desktop-electron/src/renderer/loading.html index 8def243b49..ae3725af61 100644 --- a/packages/desktop-electron/src/renderer/loading.html +++ b/packages/desktop-electron/src/renderer/loading.html @@ -4,20 +4,19 @@ OpenCode - - - - - + + + + - - - + + +
- + diff --git a/packages/desktop-electron/tsconfig.json b/packages/desktop-electron/tsconfig.json index 160f6c3fd2..9637fe03dd 100644 --- a/packages/desktop-electron/tsconfig.json +++ b/packages/desktop-electron/tsconfig.json @@ -18,5 +18,6 @@ "types": ["vite/client", "node", "electron"] }, "references": [{ "path": "../app" }], - "include": ["src", "package.json"] + "include": ["src", "package.json"], + "exclude": ["src/**/*.test.ts"] } diff --git a/packages/ui/src/components/font.tsx b/packages/ui/src/components/font.tsx index bcb8863c89..f735747a49 100644 --- a/packages/ui/src/components/font.tsx +++ b/packages/ui/src/components/font.tsx @@ -1,3 +1,4 @@ +import { Show } from "solid-js" import { Style, Link } from "@solidjs/meta" import inter from "../assets/fonts/inter.woff2" import ibmPlexMonoRegular from "../assets/fonts/ibm-plex-mono.woff2" @@ -166,8 +167,10 @@ export const Font = () => { } ${monoNerdCss} `} - - + + + + ) } From d481f64bdeaca91226e66c0e7888c7a10ba631f7 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:38:56 +1000 Subject: [PATCH 126/200] fix(electron): theme Windows titlebar overlay (#16843) Co-authored-by: Brendan Allan --- packages/app/src/app.tsx | 9 ++++- packages/app/src/components/titlebar.tsx | 4 +-- packages/desktop-electron/src/main/ipc.ts | 8 ++++- packages/desktop-electron/src/main/windows.ts | 35 +++++++++++++------ .../desktop-electron/src/preload/index.ts | 1 + .../desktop-electron/src/preload/types.ts | 4 +++ packages/ui/src/theme/context.tsx | 15 +++++--- 7 files changed, 56 insertions(+), 20 deletions(-) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 52a1dac6a2..2790e7d3ca 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -62,6 +62,9 @@ declare global { deepLinks?: string[] wsl?: boolean } + api?: { + setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise + } } } @@ -115,7 +118,11 @@ export function AppBaseProviders(props: ParentProps) { return ( - + { + void window.api?.setTitlebar?.({ mode }) + }} + > }> diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index b45f811501..3e2374f434 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -1,4 +1,4 @@ -import { createEffect, createMemo, Show, untrack } from "solid-js" +import { createEffect, createMemo, onCleanup, Show, untrack } from "solid-js" import { createStore } from "solid-js/store" import { useLocation, useNavigate, useParams } from "@solidjs/router" import { IconButton } from "@opencode-ai/ui/icon-button" @@ -282,7 +282,7 @@ export function Titlebar() { >
-
+ {!tauriApi() &&
}
diff --git a/packages/desktop-electron/src/main/ipc.ts b/packages/desktop-electron/src/main/ipc.ts index bbb5379bb7..c0d8773c29 100644 --- a/packages/desktop-electron/src/main/ipc.ts +++ b/packages/desktop-electron/src/main/ipc.ts @@ -2,8 +2,9 @@ import { execFile } from "node:child_process" import { BrowserWindow, Notification, app, clipboard, dialog, ipcMain, shell } from "electron" import type { IpcMainEvent, IpcMainInvokeEvent } from "electron" -import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types" +import type { InitStep, ServerReadyData, SqliteMigrationProgress, TitlebarTheme, WslConfig } from "../preload/types" import { getStore } from "./store" +import { setTitlebar } from "./windows" type Deps = { killSidecar: () => void @@ -161,6 +162,11 @@ export function registerIpcHandlers(deps: Deps) { ipcMain.handle("get-zoom-factor", (event: IpcMainInvokeEvent) => event.sender.getZoomFactor()) ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => event.sender.setZoomFactor(factor)) + ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => { + const win = BrowserWindow.fromWebContents(event.sender) + if (!win) return + setTitlebar(win, theme) + }) } export function sendSqliteMigrationProgress(win: BrowserWindow, progress: SqliteMigrationProgress) { diff --git a/packages/desktop-electron/src/main/windows.ts b/packages/desktop-electron/src/main/windows.ts index 9178457f8d..d4ec5ac790 100644 --- a/packages/desktop-electron/src/main/windows.ts +++ b/packages/desktop-electron/src/main/windows.ts @@ -1,7 +1,8 @@ import windowState from "electron-window-state" -import { app, BrowserWindow, nativeImage } from "electron" +import { app, BrowserWindow, nativeImage, nativeTheme } from "electron" import { dirname, join } from "node:path" import { fileURLToPath } from "node:url" +import type { TitlebarTheme } from "../preload/types" type Globals = { updaterEnabled: boolean @@ -20,6 +21,24 @@ function iconPath() { return join(iconsDir(), `icon.${ext}`) } +function tone() { + return nativeTheme.shouldUseDarkColors ? "dark" : "light" +} + +function overlay(theme: Partial = {}) { + const mode = theme.mode ?? tone() + return { + color: "#00000000", + symbolColor: mode === "dark" ? "white" : "black", + height: 40, + } +} + +export function setTitlebar(win: BrowserWindow, theme: Partial = {}) { + if (process.platform !== "win32") return + win.setTitleBarOverlay(overlay(theme)) +} + export function setDockIcon() { if (process.platform !== "darwin") return app.dock?.setIcon(nativeImage.createFromPath(join(iconsDir(), "128x128@2x.png"))) @@ -31,6 +50,7 @@ export function createMainWindow(globals: Globals) { defaultHeight: 800, }) + const mode = tone() const win = new BrowserWindow({ x: state.x, y: state.y, @@ -49,11 +69,7 @@ export function createMainWindow(globals: Globals) { ? { frame: false, titleBarStyle: "hidden" as const, - titleBarOverlay: { - color: "transparent", - symbolColor: "#999", - height: 40, - }, + titleBarOverlay: overlay({ mode }), } : {}), webPreferences: { @@ -71,6 +87,7 @@ export function createMainWindow(globals: Globals) { } export function createLoadingWindow(globals: Globals) { + const mode = tone() const win = new BrowserWindow({ width: 640, height: 480, @@ -83,11 +100,7 @@ export function createLoadingWindow(globals: Globals) { ? { frame: false, titleBarStyle: "hidden" as const, - titleBarOverlay: { - color: "transparent", - symbolColor: "#999", - height: 40, - }, + titleBarOverlay: overlay({ mode }), } : {}), webPreferences: { diff --git a/packages/desktop-electron/src/preload/index.ts b/packages/desktop-electron/src/preload/index.ts index a6520ab424..c1ed9afd22 100644 --- a/packages/desktop-electron/src/preload/index.ts +++ b/packages/desktop-electron/src/preload/index.ts @@ -57,6 +57,7 @@ const api: ElectronAPI = { relaunch: () => ipcRenderer.send("relaunch"), getZoomFactor: () => ipcRenderer.invoke("get-zoom-factor"), setZoomFactor: (factor) => ipcRenderer.invoke("set-zoom-factor", factor), + setTitlebar: (theme) => ipcRenderer.invoke("set-titlebar", theme), loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"), runUpdater: (alertOnFail) => ipcRenderer.invoke("run-updater", alertOnFail), checkUpdate: () => ipcRenderer.invoke("check-update"), diff --git a/packages/desktop-electron/src/preload/types.ts b/packages/desktop-electron/src/preload/types.ts index af5410f5f5..43bdf1e6c6 100644 --- a/packages/desktop-electron/src/preload/types.ts +++ b/packages/desktop-electron/src/preload/types.ts @@ -10,6 +10,9 @@ export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { export type WslConfig = { enabled: boolean } export type LinuxDisplayBackend = "wayland" | "auto" +export type TitlebarTheme = { + mode: "light" | "dark" +} export type ElectronAPI = { killSidecar: () => Promise @@ -57,6 +60,7 @@ export type ElectronAPI = { relaunch: () => void getZoomFactor: () => Promise setZoomFactor: (factor: number) => Promise + setTitlebar: (theme: TitlebarTheme) => Promise loadingWindowComplete: () => void runUpdater: (alertOnFail: boolean) => Promise checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }> diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx index cda967697c..ad82a088d1 100644 --- a/packages/ui/src/theme/context.tsx +++ b/packages/ui/src/theme/context.tsx @@ -77,7 +77,7 @@ function cacheThemeVariants(theme: DesktopTheme, themeId: string) { export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ name: "Theme", - init: (props: { defaultTheme?: string }) => { + init: (props: { defaultTheme?: string; onThemeApplied?: (theme: DesktopTheme, mode: "light" | "dark") => void }) => { const [store, setStore] = createStore({ themes: DEFAULT_THEMES as Record, themeId: normalize(props.defaultTheme) ?? "oc-2", @@ -119,10 +119,15 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ } }) + const applyTheme = (theme: DesktopTheme, themeId: string, mode: "light" | "dark") => { + applyThemeCss(theme, themeId, mode) + props.onThemeApplied?.(theme, mode) + } + createEffect(() => { const theme = store.themes[store.themeId] if (theme) { - applyThemeCss(theme, store.themeId, store.mode) + applyTheme(theme, store.themeId, store.mode) } }) @@ -171,7 +176,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ ? getSystemMode() : store.previewScheme : store.mode - applyThemeCss(theme, next, previewMode) + applyTheme(theme, next, previewMode) }, previewColorScheme: (scheme: ColorScheme) => { setStore("previewScheme", scheme) @@ -179,7 +184,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ const id = store.previewThemeId ?? store.themeId const theme = store.themes[id] if (theme) { - applyThemeCss(theme, id, previewMode) + applyTheme(theme, id, previewMode) } }, commitPreview: () => { @@ -197,7 +202,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ setStore("previewScheme", null) const theme = store.themes[store.themeId] if (theme) { - applyThemeCss(theme, store.themeId, store.mode) + applyTheme(theme, store.themeId, store.mode) } }, } From c9c0318e0e5c2fcd80fc1c32a1ccfe360f182f90 Mon Sep 17 00:00:00 2001 From: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Date: Thu, 12 Mar 2026 07:48:44 +0100 Subject: [PATCH 127/200] fix(desktop): set default WebSocket username and prevent repeated calling of terminal spawn properly closing the terminal (#17061) --- packages/app/src/components/dialog-select-server.tsx | 10 ++++++---- packages/app/src/components/terminal.tsx | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index 4813ecc45d..3ecd269104 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -16,6 +16,8 @@ import { usePlatform } from "@/context/platform" import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server" import { checkServerHealth, type ServerHealth } from "@/utils/server-health" +const DEFAULT_USERNAME = "opencode" + interface ServerFormProps { value: string name: string @@ -178,7 +180,7 @@ export function DialogSelectServer() { addServer: { url: "", name: "", - username: "", + username: DEFAULT_USERNAME, password: "", adding: false, error: "", @@ -201,7 +203,7 @@ export function DialogSelectServer() { setStore("addServer", { url: "", name: "", - username: "", + username: DEFAULT_USERNAME, password: "", adding: false, error: "", @@ -362,8 +364,8 @@ export function DialogSelectServer() { http: { url: normalized }, } if (store.addServer.name.trim()) conn.displayName = store.addServer.name.trim() - if (store.addServer.username) conn.http.username = store.addServer.username if (store.addServer.password) conn.http.password = store.addServer.password + if (store.addServer.password && store.addServer.username) conn.http.username = store.addServer.username const result = await checkServerHealth(conn.http, fetcher) setStore("addServer", { adding: false }) if (!result.healthy) { @@ -441,7 +443,7 @@ export function DialogSelectServer() { showForm: true, url: "", name: "", - username: "", + username: DEFAULT_USERNAME, password: "", error: "", status: undefined, diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index 120af0a172..fda66917f4 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -452,7 +452,7 @@ export const Terminal = (props: TerminalProps) => { url.searchParams.set("directory", sdk.directory) url.searchParams.set("cursor", String(start !== undefined ? start : restore ? -1 : 0)) url.protocol = url.protocol === "https:" ? "wss:" : "ws:" - url.username = server.current?.http.username ?? "" + url.username = server.current?.http.username ?? "opencode" url.password = server.current?.http.password ?? "" const socket = new WebSocket(url) From 328c6de80d51704c09bdd962df2ddf5b9d7c82ea Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:35:26 +1000 Subject: [PATCH 128/200] Fix terminal e2e flakiness with a real terminal driver (#17144) --- packages/app/e2e/AGENTS.md | 9 +++ packages/app/e2e/actions.ts | 53 +++++++++++++++ packages/app/e2e/fixtures.ts | 9 +++ .../e2e/prompt/prompt-slash-terminal.spec.ts | 3 +- .../e2e/settings/settings-keybinds.spec.ts | 4 +- .../app/e2e/terminal/terminal-init.spec.ts | 6 +- .../app/e2e/terminal/terminal-tabs.spec.ts | 21 ++---- packages/app/e2e/terminal/terminal.spec.ts | 3 +- packages/app/src/components/terminal.tsx | 19 ++++-- packages/app/src/testing/terminal.ts | 64 +++++++++++++++++++ 10 files changed, 165 insertions(+), 26 deletions(-) create mode 100644 packages/app/src/testing/terminal.ts diff --git a/packages/app/e2e/AGENTS.md b/packages/app/e2e/AGENTS.md index 8bfbd111b2..cb8080fb25 100644 --- a/packages/app/e2e/AGENTS.md +++ b/packages/app/e2e/AGENTS.md @@ -70,6 +70,8 @@ test("test description", async ({ page, sdk, gotoSession }) => { - `openSettings(page)` - Open settings dialog - `closeDialog(page, dialog)` - Close any dialog - `openSidebar(page)` / `closeSidebar(page)` - Toggle sidebar +- `waitTerminalReady(page, { term? })` - Wait for a mounted terminal to connect and finish rendering output +- `runTerminal(page, { cmd, token, term?, timeout? })` - Type into the terminal via the browser and wait for rendered output - `withSession(sdk, title, callback)` - Create temp session - `withProject(...)` - Create temp project/workspace - `sessionIDFromUrl(url)` - Read session ID from URL @@ -167,6 +169,13 @@ await page.keyboard.press(`${modKey}+B`) // Toggle sidebar await page.keyboard.press(`${modKey}+Comma`) // Open settings ``` +### Terminal Tests + +- In terminal tests, type through the browser. Do not write to the PTY through the SDK. +- Use `waitTerminalReady(page, { term? })` and `runTerminal(page, { cmd, token, term?, timeout? })` from `actions.ts`. +- These helpers use the fixture-enabled test-only terminal driver and wait for output after the terminal writer settles. +- Avoid `waitForTimeout` and custom DOM or `data-*` readiness checks. + ## Writing New Tests 1. Choose appropriate folder or create new one diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts index 86147dc65d..f721e0a7c9 100644 --- a/packages/app/e2e/actions.ts +++ b/packages/app/e2e/actions.ts @@ -3,6 +3,7 @@ import fs from "node:fs/promises" import os from "node:os" import path from "node:path" import { execSync } from "node:child_process" +import { terminalAttr, type E2EWindow } from "../src/testing/terminal" import { createSdk, modKey, resolveDirectory, serverUrl } from "./utils" import { dropdownMenuTriggerSelector, @@ -15,6 +16,7 @@ import { listItemSelector, listItemKeySelector, listItemKeyStartsWithSelector, + terminalSelector, workspaceItemSelector, workspaceMenuTriggerSelector, } from "./selectors" @@ -28,6 +30,57 @@ export async function defocus(page: Page) { .catch(() => undefined) } +async function terminalID(term: Locator) { + const id = await term.getAttribute(terminalAttr) + if (id) return id + throw new Error(`Active terminal missing ${terminalAttr}`) +} + +async function terminalReady(page: Page, term?: Locator) { + const next = term ?? page.locator(terminalSelector).first() + const id = await terminalID(next) + return page.evaluate((id) => { + const state = (window as E2EWindow).__opencode_e2e?.terminal?.terminals?.[id] + return !!state?.connected && (state.settled ?? 0) > 0 + }, id) +} + +async function terminalHas(page: Page, input: { term?: Locator; token: string }) { + const next = input.term ?? page.locator(terminalSelector).first() + const id = await terminalID(next) + return page.evaluate( + (input) => { + const state = (window as E2EWindow).__opencode_e2e?.terminal?.terminals?.[input.id] + return state?.rendered.includes(input.token) ?? false + }, + { id, token: input.token }, + ) +} + +export async function waitTerminalReady(page: Page, input?: { term?: Locator; timeout?: number }) { + const term = input?.term ?? page.locator(terminalSelector).first() + const timeout = input?.timeout ?? 10_000 + await expect(term).toBeVisible() + await expect(term.locator("textarea")).toHaveCount(1) + await expect + .poll(() => terminalReady(page, term), { timeout }) + .toBe(true) +} + +export async function runTerminal(page: Page, input: { cmd: string; token: string; term?: Locator; timeout?: number }) { + const term = input.term ?? page.locator(terminalSelector).first() + const timeout = input.timeout ?? 10_000 + await waitTerminalReady(page, { term, timeout }) + const textarea = term.locator("textarea") + await term.click() + await expect(textarea).toBeFocused() + await page.keyboard.type(input.cmd) + await page.keyboard.press("Enter") + await expect + .poll(() => terminalHas(page, { term, token: input.token }), { timeout }) + .toBe(true) +} + export async function openPalette(page: Page) { await defocus(page) await page.keyboard.press(`${modKey}+P`) diff --git a/packages/app/e2e/fixtures.ts b/packages/app/e2e/fixtures.ts index 6a35c6901e..cf59eeb476 100644 --- a/packages/app/e2e/fixtures.ts +++ b/packages/app/e2e/fixtures.ts @@ -1,4 +1,5 @@ import { test as base, expect, type Page } from "@playwright/test" +import type { E2EWindow } from "../src/testing/terminal" import { cleanupSession, cleanupTestProject, createTestProject, seedProjects, sessionIDFromUrl } from "./actions" import { promptSelector } from "./selectors" import { createSdk, dirSlug, getWorktree, sessionPath } from "./utils" @@ -91,6 +92,14 @@ export const test = base.extend({ async function seedStorage(page: Page, input: { directory: string; extra?: string[] }) { await seedProjects(page, input) await page.addInitScript(() => { + const win = window as E2EWindow + win.__opencode_e2e = { + ...win.__opencode_e2e, + terminal: { + enabled: true, + terminals: {}, + }, + } localStorage.setItem( "opencode.global.dat:model", JSON.stringify({ diff --git a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts index fa884b752d..100d1878ab 100644 --- a/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts +++ b/packages/app/e2e/prompt/prompt-slash-terminal.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from "../fixtures" +import { waitTerminalReady } from "../actions" import { promptSelector, terminalSelector } from "../selectors" test("/terminal toggles the terminal panel", async ({ page, gotoSession }) => { @@ -13,7 +14,7 @@ test("/terminal toggles the terminal panel", async ({ page, gotoSession }) => { await prompt.fill("/terminal") await expect(slash).toBeVisible() await page.keyboard.press("Enter") - await expect(terminal).toBeVisible() + await waitTerminalReady(page, { term: terminal }) // Terminal panel retries focus (immediate, RAF, 120ms, 240ms) after opening, // which can steal focus from the prompt and prevent fill() from triggering diff --git a/packages/app/e2e/settings/settings-keybinds.spec.ts b/packages/app/e2e/settings/settings-keybinds.spec.ts index e0d590b31a..9fc2a50ad3 100644 --- a/packages/app/e2e/settings/settings-keybinds.spec.ts +++ b/packages/app/e2e/settings/settings-keybinds.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "../fixtures" -import { openSettings, closeDialog, withSession } from "../actions" +import { openSettings, closeDialog, waitTerminalReady, withSession } from "../actions" import { keybindButtonSelector, terminalSelector } from "../selectors" import { modKey } from "../utils" @@ -302,7 +302,7 @@ test("changing terminal toggle keybind works", async ({ page, gotoSession }) => await expect(terminal).not.toBeVisible() await page.keyboard.press(`${modKey}+Y`) - await expect(terminal).toBeVisible() + await waitTerminalReady(page, { term: terminal }) await page.keyboard.press(`${modKey}+Y`) await expect(terminal).not.toBeVisible() diff --git a/packages/app/e2e/terminal/terminal-init.spec.ts b/packages/app/e2e/terminal/terminal-init.spec.ts index 18991bf763..d9bbfa2bed 100644 --- a/packages/app/e2e/terminal/terminal-init.spec.ts +++ b/packages/app/e2e/terminal/terminal-init.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from "../fixtures" +import { waitTerminalReady } from "../actions" import { promptSelector, terminalSelector } from "../selectors" import { terminalToggleKey } from "../utils" @@ -13,8 +14,7 @@ test("smoke terminal mounts and can create a second tab", async ({ page, gotoSes await page.keyboard.press(terminalToggleKey) } - await expect(terminals.first()).toBeVisible() - await expect(terminals.first().locator("textarea")).toHaveCount(1) + await waitTerminalReady(page, { term: terminals.first() }) await expect(terminals).toHaveCount(1) // Ghostty captures a lot of keybinds when focused; move focus back @@ -24,5 +24,5 @@ test("smoke terminal mounts and can create a second tab", async ({ page, gotoSes await expect(tabs).toHaveCount(2) await expect(terminals).toHaveCount(1) - await expect(terminals.first().locator("textarea")).toHaveCount(1) + await waitTerminalReady(page, { term: terminals.first() }) }) diff --git a/packages/app/e2e/terminal/terminal-tabs.spec.ts b/packages/app/e2e/terminal/terminal-tabs.spec.ts index afa6254cd0..ca1f7eee8b 100644 --- a/packages/app/e2e/terminal/terminal-tabs.spec.ts +++ b/packages/app/e2e/terminal/terminal-tabs.spec.ts @@ -1,4 +1,5 @@ import type { Page } from "@playwright/test" +import { runTerminal, waitTerminalReady } from "../actions" import { test, expect } from "../fixtures" import { terminalSelector } from "../selectors" import { terminalToggleKey, workspacePersistKey } from "../utils" @@ -17,16 +18,7 @@ async function open(page: Page) { const terminal = page.locator(terminalSelector) const visible = await terminal.isVisible().catch(() => false) if (!visible) await page.keyboard.press(terminalToggleKey) - await expect(terminal).toBeVisible() - await expect(terminal.locator("textarea")).toHaveCount(1) -} - -async function run(page: Page, cmd: string) { - const terminal = page.locator(terminalSelector) - await expect(terminal).toBeVisible() - await terminal.click() - await page.keyboard.type(cmd) - await page.keyboard.press("Enter") + await waitTerminalReady(page, { term: terminal }) } async function store(page: Page, key: string) { @@ -56,15 +48,16 @@ test("inactive terminal tab buffers persist across tab switches", async ({ page, await gotoSession() await open(page) - await run(page, `echo ${one}`) + await runTerminal(page, { cmd: `echo ${one}`, token: one }) await page.getByRole("button", { name: /new terminal/i }).click() await expect(tabs).toHaveCount(2) - await run(page, `echo ${two}`) + await runTerminal(page, { cmd: `echo ${two}`, token: two }) await first.click() await expect(first).toHaveAttribute("aria-selected", "true") + await expect .poll( async () => { @@ -76,7 +69,7 @@ test("inactive terminal tab buffers persist across tab switches", async ({ page, second: second.includes(two), } }, - { timeout: 30_000 }, + { timeout: 5_000 }, ) .toEqual({ first: false, second: true }) @@ -93,7 +86,7 @@ test("inactive terminal tab buffers persist across tab switches", async ({ page, second: second.includes(two), } }, - { timeout: 30_000 }, + { timeout: 5_000 }, ) .toEqual({ first: true, second: false }) }) diff --git a/packages/app/e2e/terminal/terminal.spec.ts b/packages/app/e2e/terminal/terminal.spec.ts index ef88aa34e5..768f7c1821 100644 --- a/packages/app/e2e/terminal/terminal.spec.ts +++ b/packages/app/e2e/terminal/terminal.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from "../fixtures" +import { waitTerminalReady } from "../actions" import { terminalSelector } from "../selectors" import { terminalToggleKey } from "../utils" @@ -13,5 +14,5 @@ test("terminal panel can be toggled", async ({ page, gotoSession }) => { } await page.keyboard.press(terminalToggleKey) - await expect(terminal).toBeVisible() + await waitTerminalReady(page, { term: terminal }) }) diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index fda66917f4..8409032938 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -10,6 +10,7 @@ import { useSDK } from "@/context/sdk" import { useServer } from "@/context/server" import { monoFontFamily, useSettings } from "@/context/settings" import type { LocalPTY } from "@/context/terminal" +import { terminalAttr, terminalProbe } from "@/testing/terminal" import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters" import { terminalWriter } from "@/utils/terminal-writer" @@ -160,6 +161,7 @@ export const Terminal = (props: TerminalProps) => { let container!: HTMLDivElement const [local, others] = splitProps(props, ["pty", "class", "classList", "autoFocus", "onConnect", "onConnectError"]) const id = local.pty.id + const probe = terminalProbe(id) const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : "" const restoreSize = restore && @@ -326,6 +328,9 @@ export const Terminal = (props: TerminalProps) => { } onMount(() => { + probe.init() + cleanups.push(() => probe.drop()) + const run = async () => { const loaded = await loadGhostty() if (disposed) return @@ -353,7 +358,13 @@ export const Terminal = (props: TerminalProps) => { } ghostty = g term = t - output = terminalWriter((data, done) => t.write(data, done)) + output = terminalWriter((data, done) => + t.write(data, () => { + probe.render(data) + probe.settle() + done?.() + }), + ) t.attachCustomKeyEventHandler((event) => { const key = event.key.toLowerCase() @@ -441,10 +452,6 @@ export const Terminal = (props: TerminalProps) => { startResize() } - // t.onScroll((ydisp) => { - // console.log("Scroll position:", ydisp) - // }) - const once = { value: false } let closing = false @@ -460,6 +467,7 @@ export const Terminal = (props: TerminalProps) => { ws = socket const handleOpen = () => { + probe.connect() local.onConnect?.() scheduleSize(t.cols, t.rows) } @@ -560,6 +568,7 @@ export const Terminal = (props: TerminalProps) => {
+ } + } +} + +const seed = (): TerminalProbeState => ({ + connected: false, + rendered: "", + settled: 0, +}) + +const root = () => { + if (typeof window === "undefined") return + const state = (window as E2EWindow).__opencode_e2e?.terminal + if (!state?.enabled) return + state.terminals ??= {} + return state.terminals +} + +export const terminalProbe = (id: string) => { + const set = (next: Partial) => { + const terms = root() + if (!terms) return + terms[id] = { ...(terms[id] ?? seed()), ...next } + } + + return { + init() { + set(seed()) + }, + connect() { + set({ connected: true }) + }, + render(data: string) { + const terms = root() + if (!terms) return + const prev = terms[id] ?? seed() + terms[id] = { ...prev, rendered: prev.rendered + data } + }, + settle() { + const terms = root() + if (!terms) return + const prev = terms[id] ?? seed() + terms[id] = { ...prev, settled: prev.settled + 1 } + }, + drop() { + const terms = root() + if (!terms) return + delete terms[id] + }, + } +} From 51835ecf90e23b34957f4dde843bbba1134f17fe Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 12 Mar 2026 07:36:35 +0000 Subject: [PATCH 129/200] chore: generate --- packages/app/e2e/actions.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts index f721e0a7c9..a56001248d 100644 --- a/packages/app/e2e/actions.ts +++ b/packages/app/e2e/actions.ts @@ -62,9 +62,7 @@ export async function waitTerminalReady(page: Page, input?: { term?: Locator; ti const timeout = input?.timeout ?? 10_000 await expect(term).toBeVisible() await expect(term.locator("textarea")).toHaveCount(1) - await expect - .poll(() => terminalReady(page, term), { timeout }) - .toBe(true) + await expect.poll(() => terminalReady(page, term), { timeout }).toBe(true) } export async function runTerminal(page: Page, input: { cmd: string; token: string; term?: Locator; timeout?: number }) { @@ -76,9 +74,7 @@ export async function runTerminal(page: Page, input: { cmd: string; token: strin await expect(textarea).toBeFocused() await page.keyboard.type(input.cmd) await page.keyboard.press("Enter") - await expect - .poll(() => terminalHas(page, { term, token: input.token }), { timeout }) - .toBe(true) + await expect.poll(() => terminalHas(page, { term, token: input.token }), { timeout }).toBe(true) } export async function openPalette(page: Page) { From b76ead3fe80a6159fdbfcc9b82c7c6318be68e7f Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 12 Mar 2026 16:10:52 +0800 Subject: [PATCH 130/200] refactor(desktop): rework default server initialization and connection handling (#16965) --- bun.lock | 2 + packages/app/package.json | 3 +- packages/app/src/app.tsx | 128 +++++++++- .../src/components/dialog-select-server.tsx | 54 ++-- .../app/src/components/status-popover.tsx | 12 +- packages/app/src/context/platform.tsx | 5 +- packages/app/src/context/server.tsx | 8 +- packages/app/src/entry.tsx | 33 ++- packages/app/src/utils/server-health.ts | 8 + packages/desktop-electron/package.json | 1 + packages/desktop-electron/src/main/index.ts | 147 ++++------- packages/desktop-electron/src/main/server.ts | 45 +--- .../desktop-electron/src/preload/types.ts | 1 + .../desktop-electron/src/renderer/index.tsx | 94 ++++--- .../desktop-electron/src/renderer/loading.tsx | 7 +- packages/desktop/src-tauri/src/lib.rs | 236 +++++------------- packages/desktop/src-tauri/src/server.rs | 105 +------- packages/desktop/src/bindings.ts | 1 - packages/desktop/src/index.tsx | 102 ++++---- 19 files changed, 404 insertions(+), 588 deletions(-) diff --git a/bun.lock b/bun.lock index 5e66e3e167..248caffa8d 100644 --- a/bun.lock +++ b/bun.lock @@ -46,6 +46,7 @@ "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", + "effect": "4.0.0-beta.29", "fuzzysort": "catalog:", "ghostty-web": "github:anomalyco/ghostty-web#main", "luxon": "catalog:", @@ -226,6 +227,7 @@ "@solid-primitives/storage": "catalog:", "@solidjs/meta": "catalog:", "@solidjs/router": "0.15.4", + "effect": "4.0.0-beta.29", "electron-log": "^5", "electron-store": "^10", "electron-updater": "^6", diff --git a/packages/app/package.json b/packages/app/package.json index 10ef17d1bf..f8e2bda514 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -45,8 +45,8 @@ "@shikijs/transformers": "3.9.2", "@solid-primitives/active-element": "2.1.3", "@solid-primitives/audio": "1.4.2", - "@solid-primitives/i18n": "2.2.1", "@solid-primitives/event-bus": "1.1.2", + "@solid-primitives/i18n": "2.2.1", "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", "@solid-primitives/scroll": "2.1.3", @@ -56,6 +56,7 @@ "@solidjs/router": "catalog:", "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", + "effect": "4.0.0-beta.29", "fuzzysort": "catalog:", "ghostty-web": "github:anomalyco/ghostty-web#main", "luxon": "catalog:", diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 2790e7d3ca..1b7ffde46a 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -1,14 +1,29 @@ import "@/index.css" -import { File } from "@opencode-ai/ui/file" import { I18nProvider } from "@opencode-ai/ui/context" import { DialogProvider } from "@opencode-ai/ui/context/dialog" import { FileComponentProvider } from "@opencode-ai/ui/context/file" import { MarkedProvider } from "@opencode-ai/ui/context/marked" +import { File } from "@opencode-ai/ui/file" import { Font } from "@opencode-ai/ui/font" +import { Splash } from "@opencode-ai/ui/logo" import { ThemeProvider } from "@opencode-ai/ui/theme" import { MetaProvider } from "@solidjs/meta" -import { BaseRouterProps, Navigate, Route, Router } from "@solidjs/router" -import { Component, ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js" +import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router" +import { type Duration, Effect } from "effect" +import { + type Component, + createResource, + createSignal, + ErrorBoundary, + For, + type JSX, + lazy, + onCleanup, + type ParentProps, + Show, + Suspense, +} from "solid-js" +import { Dynamic } from "solid-js/web" import { CommandProvider } from "@/context/command" import { CommentsProvider } from "@/context/comments" import { FileProvider } from "@/context/file" @@ -22,13 +37,13 @@ import { NotificationProvider } from "@/context/notification" import { PermissionProvider } from "@/context/permission" import { usePlatform } from "@/context/platform" import { PromptProvider } from "@/context/prompt" -import { type ServerConnection, ServerProvider, useServer } from "@/context/server" +import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server" import { SettingsProvider } from "@/context/settings" import { TerminalProvider } from "@/context/terminal" import DirectoryLayout from "@/pages/directory-layout" import Layout from "@/pages/layout" import { ErrorPage } from "./pages/error" -import { Dynamic } from "solid-js/web" +import { useCheckServerHealth } from "./utils/server-health" const Home = lazy(() => import("@/pages/home")) const Session = lazy(() => import("@/pages/session")) @@ -139,15 +154,108 @@ export function AppBaseProviders(props: ParentProps) { ) } -function ServerKey(props: ParentProps) { +const effectMinDuration = + (duration: Duration.Input) => + (e: Effect.Effect) => + Effect.all([e, Effect.sleep(duration)], { concurrency: "unbounded" }).pipe(Effect.map((v) => v[0])) + +function ConnectionGate(props: ParentProps) { const server = useServer() + const checkServerHealth = useCheckServerHealth() + + const [checkMode, setCheckMode] = createSignal<"blocking" | "background">("blocking") + + // performs repeated health check with a grace period for + // non-http connections, otherwise fails instantly + const [startupHealthCheck, healthCheckActions] = createResource(() => + Effect.gen(function* () { + if (!server.current) return true + const { http, type } = server.current + + while (true) { + const res = yield* Effect.promise(() => checkServerHealth(http)) + if (res.healthy) return true + if (checkMode() === "background" || type === "http") return false + } + }).pipe( + effectMinDuration(checkMode() === "blocking" ? "1.2 seconds" : 0), + Effect.timeoutOrElse({ duration: "10 seconds", onTimeout: () => Effect.succeed(false) }), + Effect.ensuring(Effect.sync(() => setCheckMode("background"))), + Effect.runPromise, + ), + ) + return ( - - {props.children} + + +
+ } + > + { + if (checkMode() === "background") healthCheckActions.refetch() + }} + onServerSelected={(key) => { + setCheckMode("blocking") + server.setActive(key) + healthCheckActions.refetch() + }} + /> + } + > + {props.children} + ) } +function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: ServerConnection.Key) => void }) { + const server = useServer() + const others = () => server.list.filter((s) => ServerConnection.key(s) !== server.key) + + const timer = setInterval(() => props.onRetry?.(), 1000) + onCleanup(() => clearInterval(timer)) + + return ( +
+
+ +

+ Could not reach {server.name || server.key} +

+

Retrying automatically...

+
+ 0}> +
+ Other servers +
+ + {(conn) => { + const key = ServerConnection.key(conn) + return ( + + ) + }} + +
+
+
+
+ ) +} + export function AppInterface(props: { children?: JSX.Element defaultServer: ServerConnection.Key @@ -156,7 +264,7 @@ export function AppInterface(props: { }) { return ( - + - + ) } diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index 3ecd269104..cba401a467 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -14,7 +14,7 @@ import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server" -import { checkServerHealth, type ServerHealth } from "@/utils/server-health" +import { type ServerHealth, useCheckServerHealth } from "@/utils/server-health" const DEFAULT_USERNAME = "opencode" @@ -43,13 +43,15 @@ function showRequestError(language: ReturnType, err: unknown }) } -function useDefaultServer(platform: ReturnType, language: ReturnType) { - const [defaultUrl, defaultUrlActions] = createResource( +function useDefaultServer() { + const language = useLanguage() + const platform = usePlatform() + const [defaultKey, defaultUrlActions] = createResource( async () => { try { - const url = await platform.getDefaultServerUrl?.() - if (!url) return null - return normalizeServerUrl(url) ?? null + const key = await platform.getDefaultServer?.() + if (!key) return null + return key } catch (err) { showRequestError(language, err) return null @@ -58,20 +60,22 @@ function useDefaultServer(platform: ReturnType, language: Re { initialValue: null }, ) - const canDefault = createMemo(() => !!platform.getDefaultServerUrl && !!platform.setDefaultServerUrl) - const setDefault = async (url: string | null) => { + const canDefault = createMemo(() => !!platform.getDefaultServer && !!platform.setDefaultServer) + const setDefault = async (key: ServerConnection.Key | null) => { try { - await platform.setDefaultServerUrl?.(url) - defaultUrlActions.mutate(url) + await platform.setDefaultServer?.(key) + defaultUrlActions.mutate(key) } catch (err) { showRequestError(language, err) } } - return { defaultUrl, canDefault, setDefault } + return { defaultKey, canDefault, setDefault } } -function useServerPreview(fetcher: typeof fetch) { +function useServerPreview() { + const checkServerHealth = useCheckServerHealth() + const looksComplete = (value: string) => { const normalized = normalizeServerUrl(value) if (!normalized) return false @@ -94,7 +98,7 @@ function useServerPreview(fetcher: typeof fetch) { const http: ServerConnection.HttpBase = { url: normalized } if (username) http.username = username if (password) http.password = password - const result = await checkServerHealth(http, fetcher) + const result = await checkServerHealth(http) setStatus(result.healthy) } @@ -172,9 +176,9 @@ export function DialogSelectServer() { const server = useServer() const platform = usePlatform() const language = useLanguage() - const fetcher = platform.fetch ?? globalThis.fetch - const { defaultUrl, canDefault, setDefault } = useDefaultServer(platform, language) - const { previewStatus } = useServerPreview(fetcher) + const { defaultKey, canDefault, setDefault } = useDefaultServer() + const { previewStatus } = useServerPreview() + const checkServerHealth = useCheckServerHealth() const [store, setStore] = createStore({ status: {} as Record, addServer: { @@ -266,7 +270,7 @@ export function DialogSelectServer() { const results: Record = {} await Promise.all( items().map(async (conn) => { - results[ServerConnection.key(conn)] = await checkServerHealth(conn.http, fetcher) + results[ServerConnection.key(conn)] = await checkServerHealth(conn.http) }), ) setStore("status", reconcile(results)) @@ -366,7 +370,7 @@ export function DialogSelectServer() { if (store.addServer.name.trim()) conn.displayName = store.addServer.name.trim() if (store.addServer.password) conn.http.password = store.addServer.password if (store.addServer.password && store.addServer.username) conn.http.username = store.addServer.username - const result = await checkServerHealth(conn.http, fetcher) + const result = await checkServerHealth(conn.http) setStore("addServer", { adding: false }) if (!result.healthy) { setStore("addServer", { error: language.t("dialog.server.add.error") }) @@ -406,7 +410,7 @@ export function DialogSelectServer() { displayName: name, http: { url: normalized, username, password }, } - const result = await checkServerHealth(conn.http, fetcher) + const result = await checkServerHealth(conn.http) setStore("editServer", { busy: false }) if (!result.healthy) { setStore("editServer", { error: language.t("dialog.server.add.error") }) @@ -496,8 +500,8 @@ export function DialogSelectServer() { async function handleRemove(url: ServerConnection.Key) { server.remove(url) - if ((await platform.getDefaultServerUrl?.()) === url) { - platform.setDefaultServerUrl?.(null) + if ((await platform.getDefaultServer?.()) === url) { + platform.setDefaultServer?.(null) } } @@ -553,7 +557,7 @@ export function DialogSelectServer() { status={store.status[key]} class="flex items-center gap-3 min-w-0 flex-1" badge={ - + {language.t("dialog.server.status.default")} @@ -586,14 +590,14 @@ export function DialogSelectServer() { > {language.t("dialog.server.menu.edit")} - - setDefault(i.http.url)}> + + setDefault(key)}> {language.t("dialog.server.menu.default")} - + setDefault(null)}> {language.t("dialog.server.menu.defaultRemove")} diff --git a/packages/app/src/components/status-popover.tsx b/packages/app/src/components/status-popover.tsx index c61b319583..8073746c90 100644 --- a/packages/app/src/components/status-popover.tsx +++ b/packages/app/src/components/status-popover.tsx @@ -14,7 +14,7 @@ import { usePlatform } from "@/context/platform" import { useSDK } from "@/context/sdk" import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server" import { useSync } from "@/context/sync" -import { checkServerHealth, type ServerHealth } from "@/utils/server-health" +import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health" import { DialogSelectServer } from "./dialog-select-server" const pollMs = 10_000 @@ -53,7 +53,8 @@ const listServersByHealth = ( }) } -const useServerHealth = (servers: Accessor, fetcher: typeof fetch) => { +const useServerHealth = (servers: Accessor) => { + const checkServerHealth = useCheckServerHealth() const [status, setStatus] = createStore({} as Record) createEffect(() => { @@ -64,7 +65,7 @@ const useServerHealth = (servers: Accessor, fetcher: typ const results: Record = {} await Promise.all( list.map(async (conn) => { - results[ServerConnection.key(conn)] = await checkServerHealth(conn.http, fetcher) + results[ServerConnection.key(conn)] = await checkServerHealth(conn.http) }), ) if (dead) return @@ -168,7 +169,6 @@ export function StatusPopover() { const language = useLanguage() const navigate = useNavigate() - const fetcher = platform.fetch ?? globalThis.fetch const servers = createMemo(() => { const current = server.current const list = server.list @@ -176,10 +176,10 @@ export function StatusPopover() { if (list.every((item) => ServerConnection.key(item) !== ServerConnection.key(current))) return [current, ...list] return [current, ...list.filter((item) => ServerConnection.key(item) !== ServerConnection.key(current))] }) - const health = useServerHealth(servers, fetcher) + const health = useServerHealth(servers) const sortedServers = createMemo(() => listServersByHealth(servers(), server.key, health)) const mcp = useMcpToggle({ sync, sdk, language }) - const defaultServer = useDefaultServerKey(platform.getDefaultServerUrl) + const defaultServer = useDefaultServerKey(platform.getDefaultServer) const mcpNames = createMemo(() => Object.keys(sync.data.mcp ?? {}).sort((a, b) => a.localeCompare(b))) const mcpStatus = (name: string) => sync.data.mcp?.[name]?.status const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length) diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index 86f3321e46..b8ed58e343 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -1,6 +1,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context" import type { AsyncStorage, SyncStorage } from "@solid-primitives/storage" import type { Accessor } from "solid-js" +import { ServerConnection } from "./server" type PickerPaths = string | string[] | null type OpenDirectoryPickerOptions = { title?: string; multiple?: boolean } @@ -58,10 +59,10 @@ export type Platform = { fetch?: typeof fetch /** Get the configured default server URL (platform-specific) */ - getDefaultServerUrl?(): Promise + getDefaultServer?(): Promise /** Set the default server URL to use on app startup (platform-specific) */ - setDefaultServerUrl?(url: string | null): Promise | void + setDefaultServer?(url: ServerConnection.Key | null): Promise | void /** Get the configured WSL integration (desktop only) */ getWslEnabled?(): Promise diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 4ff777e2eb..1171ca9053 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -1,9 +1,8 @@ import { createSimpleContext } from "@opencode-ai/ui/context" import { type Accessor, batch, createEffect, createMemo, onCleanup } from "solid-js" import { createStore } from "solid-js/store" -import { usePlatform } from "@/context/platform" import { Persist, persisted } from "@/utils/persist" -import { checkServerHealth } from "@/utils/server-health" +import { useCheckServerHealth } from "@/utils/server-health" type StoredProject = { worktree: string; expanded: boolean } type StoredServer = string | ServerConnection.HttpBase | ServerConnection.Http @@ -96,7 +95,7 @@ export namespace ServerConnection { export const { use: useServer, provider: ServerProvider } = createSimpleContext({ name: "Server", init: (props: { defaultServer: ServerConnection.Key; servers?: Array }) => { - const platform = usePlatform() + const checkServerHealth = useCheckServerHealth() const [store, setStore, _, ready] = persisted( Persist.global("server", ["server.v3"]), @@ -197,8 +196,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( const isReady = createMemo(() => ready() && !!state.active) - const fetcher = platform.fetch ?? globalThis.fetch - const check = (conn: ServerConnection.Any) => checkServerHealth(conn.http, fetcher).then((x) => x.healthy) + const check = (conn: ServerConnection.Any) => checkServerHealth(conn.http).then((x) => x.healthy) createEffect(() => { const current_ = current() diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index e9c0a4397c..c62baccba5 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -98,6 +98,19 @@ if (!(root instanceof HTMLElement) && import.meta.env.DEV) { throw new Error(getRootNotFoundError()) } +const getCurrentUrl = () => { + if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" + if (import.meta.env.DEV) + return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` + return location.origin +} + +const getDefaultUrl = () => { + const lsDefault = readDefaultServerUrl() + if (lsDefault) return lsDefault + return getCurrentUrl() +} + const platform: Platform = { platform: "web", version: pkg.version, @@ -106,26 +119,20 @@ const platform: Platform = { forward, restart, notify, - getDefaultServerUrl: async () => readDefaultServerUrl(), - setDefaultServerUrl: writeDefaultServerUrl, + getDefaultServer: async () => { + const stored = readDefaultServerUrl() + return stored ? ServerConnection.Key.make(stored) : null + }, + setDefaultServer: writeDefaultServerUrl, } -const defaultUrl = iife(() => { - const lsDefault = readDefaultServerUrl() - if (lsDefault) return lsDefault - if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" - if (import.meta.env.DEV) - return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` - return location.origin -}) - if (root instanceof HTMLElement) { - const server: ServerConnection.Http = { type: "http", http: { url: defaultUrl } } + const server: ServerConnection.Http = { type: "http", http: { url: getCurrentUrl() } } render( () => ( - + ), diff --git a/packages/app/src/utils/server-health.ts b/packages/app/src/utils/server-health.ts index db4aa89bde..45a323c7be 100644 --- a/packages/app/src/utils/server-health.ts +++ b/packages/app/src/utils/server-health.ts @@ -1,3 +1,4 @@ +import { usePlatform } from "@/context/platform" import type { ServerConnection } from "@/context/server" import { createSdkForServer } from "./server" @@ -81,3 +82,10 @@ export async function checkServerHealth( .catch((error) => next(count, error)) return attempt(0).finally(() => timeout?.clear?.()) } + +export function useCheckServerHealth() { + const platform = usePlatform() + const fetcher = platform.fetch ?? globalThis.fetch + + return (http: ServerConnection.HttpBase) => checkServerHealth(http, fetcher) +} diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 45fa7355f8..4f67f81a68 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -30,6 +30,7 @@ "@solid-primitives/storage": "catalog:", "@solidjs/meta": "catalog:", "@solidjs/router": "0.15.4", + "effect": "4.0.0-beta.29", "electron-log": "^5", "electron-store": "^10", "electron-updater": "^6", diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index 7b6acd1477..64c2eb10f2 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -31,35 +31,13 @@ import { registerIpcHandlers, sendDeepLinks, sendMenuCommand, sendSqliteMigratio import { initLogging } from "./logging" import { parseMarkdown } from "./markdown" import { createMenu } from "./menu" -import { - checkHealth, - checkHealthOrAskRetry, - getDefaultServerUrl, - getSavedServerUrl, - getWslConfig, - setDefaultServerUrl, - setWslConfig, - spawnLocalServer, -} from "./server" +import { getDefaultServerUrl, getWslConfig, setDefaultServerUrl, setWslConfig, spawnLocalServer } from "./server" import { createLoadingWindow, createMainWindow, setDockIcon } from "./windows" -type ServerConnection = - | { variant: "existing"; url: string } - | { - variant: "cli" - url: string - password: null | string - health: { - wait: Promise - } - events: any - } - const initEmitter = new EventEmitter() let initStep: InitStep = { phase: "server_waiting" } let mainWindow: BrowserWindow | null = null -const loadingWindow: BrowserWindow | null = null let sidecar: CommandChild | null = null const loadingComplete = defer() @@ -131,77 +109,48 @@ function setInitStep(step: InitStep) { initEmitter.emit("step", step) } -async function setupServerConnection(): Promise { - const customUrl = await getSavedServerUrl() - - if (customUrl && (await checkHealthOrAskRetry(customUrl))) { - serverReady.resolve({ url: customUrl, password: null }) - return { variant: "existing", url: customUrl } - } - - const port = await getSidecarPort() - const hostname = "127.0.0.1" - const localUrl = `http://${hostname}:${port}` - - if (await checkHealth(localUrl)) { - serverReady.resolve({ url: localUrl, password: null }) - return { variant: "existing", url: localUrl } - } - - const password = randomUUID() - const { child, health, events } = spawnLocalServer(hostname, port, password) - sidecar = child - - return { - variant: "cli", - url: localUrl, - password, - health, - events, - } -} - async function initialize() { const needsMigration = !sqliteFileExists() const sqliteDone = needsMigration ? defer() : undefined + let overlay: BrowserWindow | null = null + + const port = await getSidecarPort() + const hostname = "127.0.0.1" + const url = `http://${hostname}:${port}` + const password = randomUUID() + + logger.log("spawning sidecar", { url }) + const { child, health, events } = spawnLocalServer(hostname, port, password) + sidecar = child + serverReady.resolve({ + url, + username: "opencode", + password, + }) const loadingTask = (async () => { - logger.log("setting up server connection") - const serverConnection = await setupServerConnection() - logger.log("server connection ready", { - variant: serverConnection.variant, - url: serverConnection.url, + logger.log("sidecar connection started", { url }) + + events.on("sqlite", (progress: SqliteMigrationProgress) => { + setInitStep({ phase: "sqlite_waiting" }) + if (overlay) sendSqliteMigrationProgress(overlay, progress) + if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress) + if (progress.type === "Done") sqliteDone?.resolve() }) - const cliHealthCheck = (() => { - if (serverConnection.variant == "cli") { - return async () => { - const { events, health } = serverConnection - events.on("sqlite", (progress: SqliteMigrationProgress) => { - setInitStep({ phase: "sqlite_waiting" }) - if (loadingWindow) sendSqliteMigrationProgress(loadingWindow, progress) - if (mainWindow) sendSqliteMigrationProgress(mainWindow, progress) - if (progress.type === "Done") sqliteDone?.resolve() - }) - await health.wait - serverReady.resolve({ - url: serverConnection.url, - password: serverConnection.password, - }) - } - } else { - serverReady.resolve({ url: serverConnection.url, password: null }) - return null - } - })() - - logger.log("server connection started") - - if (cliHealthCheck) { - if (needsMigration) await sqliteDone?.promise - cliHealthCheck?.() + if (needsMigration) { + await sqliteDone?.promise } + await Promise.race([ + health.wait, + delay(30_000).then(() => { + throw new Error("Sidecar health check timed out") + }), + ]).catch((error) => { + logger.error("sidecar health check failed", error) + }) + logger.log("loading task finished") })() @@ -211,32 +160,26 @@ async function initialize() { deepLinks: pendingDeepLinks, } - const loadingWindow = await (async () => { - if (needsMigration /** TOOD: 1 second timeout */) { - // showLoading = await Promise.race([init.then(() => false).catch(() => false), delay(1000).then(() => true)]) - const loadingWindow = createLoadingWindow(globals) - await delay(1000) - return loadingWindow - } else { - logger.log("showing main window without loading window") - mainWindow = createMainWindow(globals) - wireMenu() + wireMenu() + + if (needsMigration) { + const show = await Promise.race([loadingTask.then(() => false), delay(1_000).then(() => true)]) + if (show) { + overlay = createLoadingWindow(globals) + await delay(1_000) } - })() + } await loadingTask setInitStep({ phase: "done" }) - if (loadingWindow) { + if (overlay) { await loadingComplete.promise } - if (!mainWindow) { - mainWindow = createMainWindow(globals) - wireMenu() - } + mainWindow = createMainWindow(globals) - loadingWindow?.close() + overlay?.close() } function wireMenu() { diff --git a/packages/desktop-electron/src/main/server.ts b/packages/desktop-electron/src/main/server.ts index 92018e72e7..2d09d119f5 100644 --- a/packages/desktop-electron/src/main/server.ts +++ b/packages/desktop-electron/src/main/server.ts @@ -1,6 +1,4 @@ -import { dialog } from "electron" - -import { getConfig, serve, type CommandChild, type Config } from "./cli" +import { serve, type CommandChild } from "./cli" import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants" import { store } from "./store" @@ -31,15 +29,6 @@ export function setWslConfig(config: WslConfig) { store.set(WSL_ENABLED_KEY, config.enabled) } -export async function getSavedServerUrl(): Promise { - const direct = getDefaultServerUrl() - if (direct) return direct - - const config = await getConfig().catch(() => null) - if (!config) return null - return getServerUrlFromConfig(config) -} - export function spawnLocalServer(hostname: string, port: number, password: string) { const { child, exit, events } = serve(hostname, port, password) @@ -94,36 +83,4 @@ export async function checkHealth(url: string, password?: string | null): Promis } } -export async function checkHealthOrAskRetry(url: string): Promise { - while (true) { - if (await checkHealth(url)) return true - - const result = await dialog.showMessageBox({ - type: "warning", - message: `Could not connect to configured server:\n${url}\n\nWould you like to retry or start a local server instead?`, - title: "Connection Failed", - buttons: ["Retry", "Start Local"], - defaultId: 0, - cancelId: 1, - }) - - if (result.response === 0) continue - return false - } -} - -export function normalizeHostnameForUrl(hostname: string) { - if (hostname === "0.0.0.0") return "127.0.0.1" - if (hostname === "::") return "[::1]" - if (hostname.includes(":") && !hostname.startsWith("[")) return `[${hostname}]` - return hostname -} - -export function getServerUrlFromConfig(config: Config) { - const server = config.server - if (!server?.port) return null - const host = server.hostname ? normalizeHostnameForUrl(server.hostname) : "127.0.0.1" - return `http://${host}:${server.port}` -} - export type { CommandChild } diff --git a/packages/desktop-electron/src/preload/types.ts b/packages/desktop-electron/src/preload/types.ts index 43bdf1e6c6..ae4ca213de 100644 --- a/packages/desktop-electron/src/preload/types.ts +++ b/packages/desktop-electron/src/preload/types.ts @@ -2,6 +2,7 @@ export type InitStep = { phase: "server_waiting" } | { phase: "sqlite_waiting" } export type ServerReadyData = { url: string + username: string | null password: string | null } diff --git a/packages/desktop-electron/src/renderer/index.tsx b/packages/desktop-electron/src/renderer/index.tsx index b5193d626b..e313d5594c 100644 --- a/packages/desktop-electron/src/renderer/index.tsx +++ b/packages/desktop-electron/src/renderer/index.tsx @@ -9,9 +9,8 @@ import { ServerConnection, useCommand, } from "@opencode-ai/app" -import { Splash } from "@opencode-ai/ui/logo" import type { AsyncStorage } from "@solid-primitives/storage" -import { type Accessor, createResource, type JSX, onCleanup, onMount, Show } from "solid-js" +import { createResource, onCleanup, onMount, Show } from "solid-js" import { render } from "solid-js/web" import { MemoryRouter } from "@solidjs/router" import pkg from "../../package.json" @@ -19,7 +18,6 @@ import { initI18n, t } from "./i18n" import { UPDATER_ENABLED } from "./updater" import { webviewZoom } from "./webview-zoom" import "./styles.css" -import type { ServerReadyData } from "../preload/types" const root = document.getElementById("root") if (import.meta.env.DEV && !(root instanceof HTMLElement)) { @@ -198,11 +196,13 @@ const createPlatform = (): Platform => { await window.api.setWslConfig({ enabled }) }, - getDefaultServerUrl: async () => { - return window.api.getDefaultServerUrl().catch(() => null) + getDefaultServer: async () => { + const url = await window.api.getDefaultServerUrl().catch(() => null) + if (!url) return null + return ServerConnection.Key.make(url) }, - setDefaultServerUrl: async (url: string | null) => { + setDefaultServer: async (url: string | null) => { await window.api.setDefaultServerUrl(url) }, @@ -240,6 +240,31 @@ listenForDeepLinks() render(() => { const platform = createPlatform() + // Fetch sidecar credentials (available immediately, before health check) + const [sidecar] = createResource(() => window.api.awaitInitialization(() => undefined)) + + const [defaultServer] = createResource(() => + platform.getDefaultServer?.().then((url) => { + if (url) return ServerConnection.key({ type: "http", http: { url } }) + }), + ) + + const servers = () => { + const data = sidecar() + if (!data) return [] + const server: ServerConnection.Sidecar = { + displayName: "Local Server", + type: "sidecar", + variant: "base", + http: { + url: data.url, + username: data.username ?? undefined, + password: data.password ?? undefined, + }, + } + return [server] as ServerConnection.Any[] + } + function handleClick(e: MouseEvent) { const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null if (link?.href) { @@ -248,6 +273,12 @@ render(() => { } } + function Inner() { + const cmd = useCommand() + menuTrigger = (id) => cmd.trigger(id) + return null + } + onMount(() => { document.addEventListener("click", handleClick) onCleanup(() => { @@ -258,55 +289,20 @@ render(() => { return ( - - {(data) => { - const server: ServerConnection.Sidecar = { - displayName: "Local Server", - type: "sidecar", - variant: "base", - http: { - url: data().url, - username: "opencode", - password: data().password ?? undefined, - }, - } - - function Inner() { - const cmd = useCommand() - - menuTrigger = (id) => cmd.trigger(id) - - return null - } - + + {(_) => { return ( - + ) }} - + ) }, root!) - -// Gate component that waits for the server to be ready -function ServerGate(props: { children: (data: Accessor) => JSX.Element }) { - const [serverData] = createResource(() => window.api.awaitInitialization(() => undefined)) - console.log({ serverData }) - if (serverData.state === "errored") throw serverData.error - - return ( - - -
- } - > - {(data) => props.children(data)} - - ) -} diff --git a/packages/desktop-electron/src/renderer/loading.tsx b/packages/desktop-electron/src/renderer/loading.tsx index 1659503529..000057e0a8 100644 --- a/packages/desktop-electron/src/renderer/loading.tsx +++ b/packages/desktop-electron/src/renderer/loading.tsx @@ -1,5 +1,5 @@ -import { render } from "solid-js/web" import { MetaProvider } from "@solidjs/meta" +import { render } from "solid-js/web" import "@opencode-ai/app/index.css" import { Font } from "@opencode-ai/ui/font" import { Splash } from "@opencode-ai/ui/logo" @@ -34,7 +34,10 @@ render(() => { const listener = window.api.onSqliteMigrationProgress((progress: SqliteMigrationProgress) => { if (progress.type === "InProgress") setPercent(Math.max(0, Math.min(100, progress.value))) - if (progress.type === "Done") setPercent(100) + if (progress.type === "Done") { + setPercent(100) + setStep({ phase: "done" }) + } }) onCleanup(() => { diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs index 137692cdf7..a843ac8174 100644 --- a/packages/desktop/src-tauri/src/lib.rs +++ b/packages/desktop/src-tauri/src/lib.rs @@ -12,12 +12,10 @@ mod window_customizer; mod windows; use crate::cli::CommandChild; -use futures::{ - FutureExt, TryFutureExt, - future::{self, Shared}, -}; +use futures::{FutureExt, TryFutureExt}; use std::{ env, + future::Future, net::TcpListener, path::PathBuf, process::Command, @@ -35,7 +33,6 @@ use tokio::{ use crate::cli::{sqlite_migration::SqliteMigrationProgress, sync_cli}; use crate::constants::*; -use crate::server::get_saved_server_url; use crate::windows::{LoadingWindow, MainWindow}; #[derive(Clone, serde::Serialize, specta::Type, Debug)] @@ -43,7 +40,6 @@ struct ServerReadyData { url: String, username: Option, password: Option, - is_sidecar: bool, } #[derive(Clone, Copy, serde::Serialize, specta::Type, Debug)] @@ -65,27 +61,12 @@ struct InitState { current: watch::Receiver, } -#[derive(Clone)] struct ServerState { child: Arc>>, - status: future::Shared>>, } -impl ServerState { - pub fn new( - child: Option, - status: Shared>>, - ) -> Self { - Self { - child: Arc::new(Mutex::new(child)), - status, - } - } - - pub fn set_child(&self, child: Option) { - *self.child.lock().unwrap() = child; - } -} +/// Resolves with sidecar credentials as soon as the sidecar is spawned (before health check). +struct SidecarReady(futures::future::Shared>); #[tauri::command] #[specta::specta] @@ -110,26 +91,21 @@ fn kill_sidecar(app: AppHandle) { tracing::info!("Killed server"); } -fn get_logs() -> String { - logging::tail() -} - #[tauri::command] #[specta::specta] async fn await_initialization( - state: State<'_, ServerState>, + state: State<'_, SidecarReady>, init_state: State<'_, InitState>, events: Channel, ) -> Result { let mut rx = init_state.current.clone(); - let events = async { + let stream = async { let e = *rx.borrow(); let _ = events.send(e); while rx.changed().await.is_ok() { let step = *rx.borrow_and_update(); - let _ = events.send(step); if matches!(step, InitStep::Done) { @@ -138,10 +114,18 @@ async fn await_initialization( } }; - future::join(state.status.clone(), events) - .await - .0 - .map_err(|_| "Failed to get server status".to_string())? + // Wait for sidecar credentials (available immediately after spawn, before health check) + let data = async { + state + .inner() + .0 + .clone() + .await + .map_err(|_| "Failed to get sidecar data".to_string()) + }; + + let (result, _) = futures::future::join(data, stream).await; + result } #[tauri::command] @@ -439,22 +423,35 @@ async fn initialize(app: AppHandle) { setup_app(&app, init_rx); spawn_cli_sync_task(app.clone()); - let (server_ready_tx, server_ready_rx) = oneshot::channel(); - let server_ready_rx = server_ready_rx.shared(); - app.manage(ServerState::new(None, server_ready_rx.clone())); + // Spawn sidecar immediately - credentials are known before health check + let port = get_sidecar_port(); + let hostname = "127.0.0.1"; + let url = format!("http://{hostname}:{port}"); + let password = uuid::Uuid::new_v4().to_string(); + + tracing::info!("Spawning sidecar on {url}"); + let (child, health_check) = + server::spawn_local_server(app.clone(), hostname.to_string(), port, password.clone()); + + // Make sidecar credentials available immediately (before health check completes) + let (ready_tx, ready_rx) = oneshot::channel(); + let _ = ready_tx.send(ServerReadyData { + url: url.clone(), + username: Some("opencode".to_string()), + password: Some(password), + }); + app.manage(SidecarReady(ready_rx.shared())); + app.manage(ServerState { + child: Arc::new(Mutex::new(Some(child))), + }); let loading_window_complete = event_once_fut::(&app); - tracing::info!("Main and loading windows created"); - // SQLite migration handling: - // We only do this if the sqlite db doesn't exist, and we're expecting the sidecar to create it - // First, we spawn a task that listens for SqliteMigrationProgress events that can - // come from any invocation of the sidecar CLI. The progress is captured by a stdout stream interceptor. - // Then in the loading task, we wait for sqlite migration to complete before - // starting our health check against the server, otherwise long migrations could result in a timeout. - let needs_sqlite_migration = !sqlite_file_exists(); - let sqlite_done = needs_sqlite_migration.then(|| { + // We only do this if the sqlite db doesn't exist, and we're expecting the sidecar to create it. + // A separate loading window is shown for long migrations. + let needs_migration = !sqlite_file_exists(); + let sqlite_done = needs_migration.then(|| { tracing::info!( path = %opencode_db_path().expect("failed to get db path").display(), "Sqlite file not found, waiting for it to be generated" @@ -480,80 +477,22 @@ async fn initialize(app: AppHandle) { })) }); + // The loading task waits for SQLite migration (if needed) then for the sidecar health check. + // This is only used to drive the loading window progress - the main window is shown immediately. let loading_task = tokio::spawn({ - let app = app.clone(); - async move { - tracing::info!("Setting up server connection"); - let server_connection = setup_server_connection(app.clone()).await; - tracing::info!("Server connection setup"); - - // we delay spawning this future so that the timeout is created lazily - let cli_health_check = match server_connection { - ServerConnection::CLI { - child, - health_check, - url, - username, - password, - } => { - let app = app.clone(); - Some( - async move { - let res = timeout(Duration::from_secs(30), health_check.0).await; - let err = match res { - Ok(Ok(Ok(()))) => None, - Ok(Ok(Err(e))) => Some(e), - Ok(Err(e)) => Some(format!("Health check task failed: {e}")), - Err(_) => Some("Health check timed out".to_string()), - }; - - if let Some(err) = err { - let _ = child.kill(); - - return Err(format!( - "Failed to spawn OpenCode Server ({err}). Logs:\n{}", - get_logs() - )); - } - - tracing::info!("CLI health check OK"); - - app.state::().set_child(Some(child)); - - Ok(ServerReadyData { - url, - username, - password, - is_sidecar: true, - }) - } - .map(move |res| { - let _ = server_ready_tx.send(res); - }), - ) - } - ServerConnection::Existing { url } => { - let _ = server_ready_tx.send(Ok(ServerReadyData { - url: url.to_string(), - username: None, - password: None, - is_sidecar: false, - })); - None - } - }; - - tracing::info!("server connection started"); - - if let Some(cli_health_check) = cli_health_check { - if let Some(sqlite_done_rx) = sqlite_done { - let _ = sqlite_done_rx.await; - } - tokio::spawn(cli_health_check); + if let Some(sqlite_done_rx) = sqlite_done { + let _ = sqlite_done_rx.await; } - let _ = server_ready_rx.await; + // Wait for sidecar to become healthy (for loading window progress) + let res = timeout(Duration::from_secs(30), health_check.0).await; + match res { + Ok(Ok(Ok(()))) => tracing::info!("Sidecar health check OK"), + Ok(Ok(Err(e))) => tracing::error!("Sidecar health check failed: {e}"), + Ok(Err(e)) => tracing::error!("Sidecar health check task failed: {e}"), + Err(_) => tracing::error!("Sidecar health check timed out"), + } tracing::info!("Loading task finished"); } @@ -561,7 +500,8 @@ async fn initialize(app: AppHandle) { .map_err(|_| ()) .shared(); - let loading_window = if needs_sqlite_migration + // Show loading window for SQLite migrations if they take >1s + let loading_window = if needs_migration && timeout(Duration::from_secs(1), loading_task.clone()) .await .is_err() @@ -571,12 +511,12 @@ async fn initialize(app: AppHandle) { sleep(Duration::from_secs(1)).await; Some(loading_window) } else { - tracing::debug!("Showing main window without loading window"); - MainWindow::create(&app).expect("Failed to create main window"); - None }; + // Create main window immediately - the web app handles its own loading/health gate + MainWindow::create(&app).expect("Failed to create main window"); + let _ = loading_task.await; tracing::info!("Loading done, completing initialisation"); @@ -584,12 +524,9 @@ async fn initialize(app: AppHandle) { if loading_window.is_some() { loading_window_complete.await; - tracing::info!("Loading window completed"); } - MainWindow::create(&app).expect("Failed to create main window"); - if let Some(loading_window) = loading_window { let _ = loading_window.close(); } @@ -610,59 +547,6 @@ fn spawn_cli_sync_task(app: AppHandle) { }); } -enum ServerConnection { - Existing { - url: String, - }, - CLI { - url: String, - username: Option, - password: Option, - child: CommandChild, - health_check: server::HealthCheck, - }, -} - -async fn setup_server_connection(app: AppHandle) -> ServerConnection { - let custom_url = get_saved_server_url(&app).await; - - tracing::info!(?custom_url, "Attempting server connection"); - - if let Some(url) = &custom_url - && server::check_health_or_ask_retry(&app, url).await - { - tracing::info!(%url, "Connected to custom server"); - // If the default server is already local, no need to also spawn a sidecar - if server::is_localhost_url(url) { - return ServerConnection::Existing { url: url.clone() }; - } - // Remote default server: fall through and also spawn a local sidecar - } - - let local_port = get_sidecar_port(); - let hostname = "127.0.0.1"; - let local_url = format!("http://{hostname}:{local_port}"); - - tracing::debug!(url = %local_url, "Checking health of local server"); - if server::check_health(&local_url, None).await { - tracing::info!(url = %local_url, "Health check OK, using existing server"); - return ServerConnection::Existing { url: local_url }; - } - - let password = uuid::Uuid::new_v4().to_string(); - - tracing::info!("Spawning new local server"); - let (child, health_check) = - server::spawn_local_server(app, hostname.to_string(), local_port, password.clone()); - - ServerConnection::CLI { - url: local_url, - username: Some("opencode".to_string()), - password: Some(password), - child, - health_check, - } -} fn get_sidecar_port() -> u32 { option_env!("OPENCODE_PORT") diff --git a/packages/desktop/src-tauri/src/server.rs b/packages/desktop/src-tauri/src/server.rs index 2c43c1cc8c..070d0c71f4 100644 --- a/packages/desktop/src-tauri/src/server.rs +++ b/packages/desktop/src-tauri/src/server.rs @@ -1,7 +1,6 @@ use std::time::{Duration, Instant}; use tauri::AppHandle; -use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult}; use tauri_plugin_store::StoreExt; use tokio::task::JoinHandle; @@ -85,22 +84,6 @@ pub fn set_wsl_config(app: AppHandle, config: WslConfig) -> Result<(), String> { Ok(()) } -pub async fn get_saved_server_url(app: &tauri::AppHandle) -> Option { - if let Some(url) = get_default_server_url(app.clone()).ok().flatten() { - tracing::info!(%url, "Using desktop-specific custom URL"); - return Some(url); - } - - if let Some(cli_config) = cli::get_config(app).await - && let Some(url) = get_server_url_from_config(&cli_config) - { - tracing::info!(%url, "Using custom server URL from config"); - return Some(url); - } - - None -} - pub fn spawn_local_server( app: AppHandle, hostname: String, @@ -145,19 +128,27 @@ pub fn spawn_local_server( pub struct HealthCheck(pub JoinHandle>); -pub async fn check_health(url: &str, password: Option<&str>) -> bool { +async fn check_health(url: &str, password: Option<&str>) -> bool { let Ok(url) = reqwest::Url::parse(url) else { return false; }; let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(7)); - if url_is_localhost(&url) { + if url + .host_str() + .is_some_and(|host| { + host.eq_ignore_ascii_case("localhost") + || host + .parse::() + .is_ok_and(|ip| ip.is_loopback()) + }) + { // Some environments set proxy variables (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) without // excluding loopback. reqwest respects these by default, which can prevent the desktop // app from reaching its own local sidecar server. builder = builder.no_proxy(); - }; + } let Ok(client) = builder.build() else { return false; @@ -177,77 +168,3 @@ pub async fn check_health(url: &str, password: Option<&str>) -> bool { .map(|r| r.status().is_success()) .unwrap_or(false) } - -pub fn is_localhost_url(url: &str) -> bool { - reqwest::Url::parse(url).is_ok_and(|u| url_is_localhost(&u)) -} - -fn url_is_localhost(url: &reqwest::Url) -> bool { - url.host_str().is_some_and(|host| { - host.eq_ignore_ascii_case("localhost") - || host - .parse::() - .is_ok_and(|ip| ip.is_loopback()) - }) -} - -/// Converts a bind address hostname to a valid URL hostname for connection. -/// - `0.0.0.0` and `::` are wildcard bind addresses, not valid connect targets -/// - IPv6 addresses need brackets in URLs (e.g., `::1` -> `[::1]`) -fn normalize_hostname_for_url(hostname: &str) -> String { - // Wildcard bind addresses -> localhost equivalents - if hostname == "0.0.0.0" { - return "127.0.0.1".to_string(); - } - if hostname == "::" { - return "[::1]".to_string(); - } - - // IPv6 addresses need brackets in URLs - if hostname.contains(':') && !hostname.starts_with('[') { - return format!("[{}]", hostname); - } - - hostname.to_string() -} - -fn get_server_url_from_config(config: &cli::Config) -> Option { - let server = config.server.as_ref()?; - let port = server.port?; - tracing::debug!(port, "server.port found in OC config"); - let hostname = server - .hostname - .as_ref() - .map(|v| normalize_hostname_for_url(v)) - .unwrap_or_else(|| "127.0.0.1".to_string()); - - Some(format!("http://{}:{}", hostname, port)) -} - -pub async fn check_health_or_ask_retry(app: &AppHandle, url: &str) -> bool { - tracing::debug!(%url, "Checking health"); - loop { - if check_health(url, None).await { - return true; - } - - const RETRY: &str = "Retry"; - - let res = app.dialog() - .message(format!("Could not connect to configured server:\n{}\n\nWould you like to retry or start a local server instead?", url)) - .title("Connection Failed") - .buttons(MessageDialogButtons::OkCancelCustom(RETRY.to_string(), "Start Local".to_string())) - .blocking_show_with_result(); - - match res { - MessageDialogResult::Custom(name) if name == RETRY => { - continue; - } - _ => { - break; - } - } - } - - false -} diff --git a/packages/desktop/src/bindings.ts b/packages/desktop/src/bindings.ts index 80548173e9..d434d3b35e 100644 --- a/packages/desktop/src/bindings.ts +++ b/packages/desktop/src/bindings.ts @@ -38,7 +38,6 @@ export type ServerReadyData = { url: string, username: string | null, password: string | null, - is_sidecar: boolean, }; export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" }; diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 9afabe918b..65149f34bc 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -9,7 +9,6 @@ import { ServerConnection, useCommand, } from "@opencode-ai/app" -import { Splash } from "@opencode-ai/ui/logo" import type { AsyncStorage } from "@solid-primitives/storage" import { getCurrentWindow } from "@tauri-apps/api/window" import { readImage } from "@tauri-apps/plugin-clipboard-manager" @@ -22,7 +21,7 @@ import { relaunch } from "@tauri-apps/plugin-process" import { open as shellOpen } from "@tauri-apps/plugin-shell" import { Store } from "@tauri-apps/plugin-store" import { check, type Update } from "@tauri-apps/plugin-updater" -import { createResource, type JSX, onCleanup, onMount, Show } from "solid-js" +import { createResource, onCleanup, onMount, Show } from "solid-js" import { render } from "solid-js/web" import pkg from "../package.json" import { initI18n, t } from "./i18n" @@ -30,7 +29,7 @@ import { UPDATER_ENABLED } from "./updater" import { webviewZoom } from "./webview-zoom" import "./styles.css" import { Channel } from "@tauri-apps/api/core" -import { commands, ServerReadyData, type InitStep } from "./bindings" +import { commands, type InitStep } from "./bindings" import { createMenu } from "./menu" const root = document.getElementById("root") @@ -348,12 +347,13 @@ const createPlatform = (): Platform => { await commands.setWslConfig({ enabled }) }, - getDefaultServerUrl: async () => { - const result = await commands.getDefaultServerUrl().catch(() => null) - return result + getDefaultServer: async () => { + const url = await commands.getDefaultServerUrl().catch(() => null) + if (!url) return null + return ServerConnection.Key.make(url) }, - setDefaultServerUrl: async (url: string | null) => { + setDefaultServer: async (url: string | null) => { await commands.setDefaultServerUrl(url) }, @@ -412,12 +412,33 @@ void listenForDeepLinks() render(() => { const platform = createPlatform() + // Fetch sidecar credentials from Rust (available immediately, before health check) + const [sidecar] = createResource(() => commands.awaitInitialization(new Channel() as any)) + const [defaultServer] = createResource(() => - platform.getDefaultServerUrl?.().then((url) => { + platform.getDefaultServer?.().then((url) => { if (url) return ServerConnection.key({ type: "http", http: { url } }) }), ) + // Build the sidecar server connection once credentials arrive + const servers = () => { + const data = sidecar() + if (!data) return [] + const http = { + url: data.url, + username: data.username ?? undefined, + password: data.password ?? undefined, + } + const server: ServerConnection.Sidecar = { + displayName: t("desktop.server.local"), + type: "sidecar", + variant: "base", + http, + } + return [server] as ServerConnection.Any[] + } + function handleClick(e: MouseEvent) { const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null if (link?.href) { @@ -426,6 +447,12 @@ render(() => { } } + function Inner() { + const cmd = useCommand() + menuTrigger = (id) => cmd.trigger(id) + return null + } + onMount(() => { document.addEventListener("click", handleClick) onCleanup(() => { @@ -436,60 +463,19 @@ render(() => { return ( - - {(data) => { - const http = { - url: data.url, - username: data.username ?? undefined, - password: data.password ?? undefined, - } - const server: ServerConnection.Any = data.is_sidecar - ? { - displayName: t("desktop.server.local"), - type: "sidecar", - variant: "base", - http, - } - : { type: "http", http } - - function Inner() { - const cmd = useCommand() - - menuTrigger = (id) => cmd.trigger(id) - - return null - } - + + {(_) => { return ( - - - - - + + + ) }} - + ) }, root!) - -// Gate component that waits for the server to be ready -function ServerGate(props: { children: (data: ServerReadyData) => JSX.Element }) { - const [serverData] = createResource(() => commands.awaitInitialization(new Channel() as any)) - if (serverData.state === "errored") throw serverData.error - - return ( - - -
-
- } - > - {(data) => props.children(data())} -
- ) -} From d8fbe0af0151c662008d8a2dbbb7c76ff5db88d4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 12 Mar 2026 08:26:49 +0000 Subject: [PATCH 131/200] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 153f78ac6d..1f1ef9d31a 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-dhL4YeSi4Lm9yDp919Fx7N2hyLUbZQa2qWoCf/50ce8=", - "aarch64-linux": "sha256-//YxCsrvYlxuvd0MtFFO+pLxjmuemyrvGzSIPxzO+rA=", - "aarch64-darwin": "sha256-c65kSWteQNaBcQUsjbXNqT61vt98JPNYo9yMNvUygCw=", - "x86_64-darwin": "sha256-hlTzEFv3nZHwlDXU65LfMC+NaqYjjyZqagdJ366CNxY=" + "x86_64-linux": "sha256-5+9GAHej/EWz87Z3eTI9yBDRL1Ko0RoXsLo/Q3t42WA=", + "aarch64-linux": "sha256-4FWmoWkLKWKita3+XHZEiDy5grOQgdzOY1AZzb0TDWE=", + "aarch64-darwin": "sha256-L4FPB1E5AtV3V6qZjmX6YM7Q/mwSYlhYyZXPXAxrLFU=", + "x86_64-darwin": "sha256-bJCcrzDF2tIsKScxw5CoW+ZRUHe4KbUWLSqiR/M7vu8=" } } From d4107d51f163b74c96895b50d653828c698d937e Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 12 Mar 2026 07:26:43 -0500 Subject: [PATCH 132/200] chore: cleanup (#17115) --- .../session/session-sortable-terminal-tab.tsx | 18 +- .../app/src/components/settings-agents.tsx | 16 - .../app/src/components/settings-commands.tsx | 16 - packages/app/src/components/settings-mcp.tsx | 16 - .../src/components/settings-permissions.tsx | 230 ------------ packages/app/src/context/global-sync.test.ts | 8 +- packages/app/src/context/global-sync.tsx | 3 - .../app/src/context/notification-index.ts | 66 ---- packages/app/src/context/notification.test.ts | 73 ---- packages/app/src/pages/layout.tsx | 6 +- packages/app/src/pages/layout/helpers.test.ts | 16 +- packages/app/src/pages/layout/helpers.ts | 8 - .../layout/sidebar-project-helpers.test.ts | 63 ---- .../pages/layout/sidebar-project-helpers.ts | 11 - .../app/src/pages/layout/sidebar-project.tsx | 18 +- .../src/pages/layout/sidebar-shell-helpers.ts | 1 - .../src/pages/layout/sidebar-shell.test.ts | 13 - .../app/src/pages/layout/sidebar-shell.tsx | 3 +- .../pages/layout/sidebar-workspace-helpers.ts | 2 - .../pages/layout/sidebar-workspace.test.ts | 13 - .../src/pages/layout/sidebar-workspace.tsx | 100 ++---- .../app/src/pages/session/composer/index.ts | 3 +- .../composer/session-composer-helpers.ts | 10 - .../composer/session-composer-state.test.ts | 2 +- .../composer/session-composer-state.ts | 31 +- packages/app/src/pages/session/helpers.ts | 56 +-- .../pages/session/session-command-helpers.ts | 10 - .../pages/session/session-prompt-dock.test.ts | 22 -- .../pages/session/session-prompt-helpers.ts | 4 - .../session/use-session-commands.test.ts | 44 --- .../pages/session/use-session-commands.tsx | 15 +- .../pages/session/use-session-hash-scroll.ts | 2 - packages/app/src/utils/dom.ts | 51 --- packages/app/src/utils/index.ts | 1 - packages/app/src/utils/speech.ts | 326 ------------------ 35 files changed, 75 insertions(+), 1202 deletions(-) delete mode 100644 packages/app/src/components/settings-agents.tsx delete mode 100644 packages/app/src/components/settings-commands.tsx delete mode 100644 packages/app/src/components/settings-mcp.tsx delete mode 100644 packages/app/src/components/settings-permissions.tsx delete mode 100644 packages/app/src/context/notification-index.ts delete mode 100644 packages/app/src/context/notification.test.ts delete mode 100644 packages/app/src/pages/layout/sidebar-project-helpers.test.ts delete mode 100644 packages/app/src/pages/layout/sidebar-project-helpers.ts delete mode 100644 packages/app/src/pages/layout/sidebar-shell-helpers.ts delete mode 100644 packages/app/src/pages/layout/sidebar-shell.test.ts delete mode 100644 packages/app/src/pages/layout/sidebar-workspace-helpers.ts delete mode 100644 packages/app/src/pages/layout/sidebar-workspace.test.ts delete mode 100644 packages/app/src/pages/session/composer/session-composer-helpers.ts delete mode 100644 packages/app/src/pages/session/session-command-helpers.ts delete mode 100644 packages/app/src/pages/session/session-prompt-dock.test.ts delete mode 100644 packages/app/src/pages/session/session-prompt-helpers.ts delete mode 100644 packages/app/src/pages/session/use-session-commands.test.ts delete mode 100644 packages/app/src/utils/dom.ts delete mode 100644 packages/app/src/utils/index.ts delete mode 100644 packages/app/src/utils/speech.ts diff --git a/packages/app/src/components/session/session-sortable-terminal-tab.tsx b/packages/app/src/components/session/session-sortable-terminal-tab.tsx index 6fe6186d51..4f49911c12 100644 --- a/packages/app/src/components/session/session-sortable-terminal-tab.tsx +++ b/packages/app/src/components/session/session-sortable-terminal-tab.tsx @@ -8,6 +8,7 @@ import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Icon } from "@opencode-ai/ui/icon" import { useTerminal, type LocalPTY } from "@/context/terminal" import { useLanguage } from "@/context/language" +import { focusTerminalById } from "@/pages/session/helpers" export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => void }): JSX.Element { const terminal = useTerminal() @@ -53,21 +54,8 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => const focus = () => { if (store.editing) return - - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur() - } - const wrapper = document.getElementById(`terminal-wrapper-${props.terminal.id}`) - const element = wrapper?.querySelector('[data-component="terminal"]') as HTMLElement - if (!element) return - - const textarea = element.querySelector("textarea") as HTMLTextAreaElement - if (textarea) { - textarea.focus() - return - } - element.focus() - element.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true })) + if (document.activeElement instanceof HTMLElement) document.activeElement.blur() + focusTerminalById(props.terminal.id) } const edit = (e?: Event) => { diff --git a/packages/app/src/components/settings-agents.tsx b/packages/app/src/components/settings-agents.tsx deleted file mode 100644 index 74a942f777..0000000000 --- a/packages/app/src/components/settings-agents.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Component } from "solid-js" -import { useLanguage } from "@/context/language" - -export const SettingsAgents: Component = () => { - // TODO: Replace this placeholder with full agents settings controls. - const language = useLanguage() - - return ( -
-
-

{language.t("settings.agents.title")}

-

{language.t("settings.agents.description")}

-
-
- ) -} diff --git a/packages/app/src/components/settings-commands.tsx b/packages/app/src/components/settings-commands.tsx deleted file mode 100644 index e158d231ce..0000000000 --- a/packages/app/src/components/settings-commands.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Component } from "solid-js" -import { useLanguage } from "@/context/language" - -export const SettingsCommands: Component = () => { - // TODO: Replace this placeholder with full commands settings controls. - const language = useLanguage() - - return ( -
-
-

{language.t("settings.commands.title")}

-

{language.t("settings.commands.description")}

-
-
- ) -} diff --git a/packages/app/src/components/settings-mcp.tsx b/packages/app/src/components/settings-mcp.tsx deleted file mode 100644 index 507e041aa8..0000000000 --- a/packages/app/src/components/settings-mcp.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Component } from "solid-js" -import { useLanguage } from "@/context/language" - -export const SettingsMcp: Component = () => { - // TODO: Replace this placeholder with full MCP settings controls. - const language = useLanguage() - - return ( -
-
-

{language.t("settings.mcp.title")}

-

{language.t("settings.mcp.description")}

-
-
- ) -} diff --git a/packages/app/src/components/settings-permissions.tsx b/packages/app/src/components/settings-permissions.tsx deleted file mode 100644 index 5c922ba44a..0000000000 --- a/packages/app/src/components/settings-permissions.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { Select } from "@opencode-ai/ui/select" -import { showToast } from "@opencode-ai/ui/toast" -import { Component, For, createMemo, type JSX } from "solid-js" -import { useGlobalSync } from "@/context/global-sync" -import { useLanguage } from "@/context/language" - -type PermissionAction = "allow" | "ask" | "deny" - -type PermissionObject = Record -type PermissionValue = PermissionAction | PermissionObject | string[] | undefined -type PermissionMap = Record - -type PermissionItem = { - id: string - title: string - description: string -} - -const ACTIONS = [ - { value: "allow", label: "settings.permissions.action.allow" }, - { value: "ask", label: "settings.permissions.action.ask" }, - { value: "deny", label: "settings.permissions.action.deny" }, -] as const - -const ITEMS = [ - { - id: "read", - title: "settings.permissions.tool.read.title", - description: "settings.permissions.tool.read.description", - }, - { - id: "edit", - title: "settings.permissions.tool.edit.title", - description: "settings.permissions.tool.edit.description", - }, - { - id: "glob", - title: "settings.permissions.tool.glob.title", - description: "settings.permissions.tool.glob.description", - }, - { - id: "grep", - title: "settings.permissions.tool.grep.title", - description: "settings.permissions.tool.grep.description", - }, - { - id: "list", - title: "settings.permissions.tool.list.title", - description: "settings.permissions.tool.list.description", - }, - { - id: "bash", - title: "settings.permissions.tool.bash.title", - description: "settings.permissions.tool.bash.description", - }, - { - id: "task", - title: "settings.permissions.tool.task.title", - description: "settings.permissions.tool.task.description", - }, - { - id: "skill", - title: "settings.permissions.tool.skill.title", - description: "settings.permissions.tool.skill.description", - }, - { - id: "lsp", - title: "settings.permissions.tool.lsp.title", - description: "settings.permissions.tool.lsp.description", - }, - { - id: "todoread", - title: "settings.permissions.tool.todoread.title", - description: "settings.permissions.tool.todoread.description", - }, - { - id: "todowrite", - title: "settings.permissions.tool.todowrite.title", - description: "settings.permissions.tool.todowrite.description", - }, - { - id: "webfetch", - title: "settings.permissions.tool.webfetch.title", - description: "settings.permissions.tool.webfetch.description", - }, - { - id: "websearch", - title: "settings.permissions.tool.websearch.title", - description: "settings.permissions.tool.websearch.description", - }, - { - id: "codesearch", - title: "settings.permissions.tool.codesearch.title", - description: "settings.permissions.tool.codesearch.description", - }, - { - id: "external_directory", - title: "settings.permissions.tool.external_directory.title", - description: "settings.permissions.tool.external_directory.description", - }, - { - id: "doom_loop", - title: "settings.permissions.tool.doom_loop.title", - description: "settings.permissions.tool.doom_loop.description", - }, -] as const - -const VALID_ACTIONS = new Set(["allow", "ask", "deny"]) - -function toMap(value: unknown): PermissionMap { - if (value && typeof value === "object" && !Array.isArray(value)) return value as PermissionMap - - const action = getAction(value) - if (action) return { "*": action } - - return {} -} - -function getAction(value: unknown): PermissionAction | undefined { - if (typeof value === "string" && VALID_ACTIONS.has(value as PermissionAction)) return value as PermissionAction - return -} - -function getRuleDefault(value: unknown): PermissionAction | undefined { - const action = getAction(value) - if (action) return action - - if (!value || typeof value !== "object" || Array.isArray(value)) return - - return getAction((value as Record)["*"]) -} - -export const SettingsPermissions: Component = () => { - const globalSync = useGlobalSync() - const language = useLanguage() - - const actions = createMemo( - (): Array<{ value: PermissionAction; label: string }> => - ACTIONS.map((action) => ({ - value: action.value, - label: language.t(action.label), - })), - ) - - const permission = createMemo(() => { - return toMap(globalSync.data.config.permission) - }) - - const actionFor = (id: string): PermissionAction => { - const value = permission()[id] - const direct = getRuleDefault(value) - if (direct) return direct - - const wildcard = getRuleDefault(permission()["*"]) - if (wildcard) return wildcard - - return "allow" - } - - const setPermission = async (id: string, action: PermissionAction) => { - const before = globalSync.data.config.permission - const map = toMap(before) - const existing = map[id] - - const nextValue = - existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing, "*": action } : action - - const rollback = (err: unknown) => { - globalSync.set("config", "permission", before) - const message = err instanceof Error ? err.message : String(err) - showToast({ title: language.t("settings.permissions.toast.updateFailed.title"), description: message }) - } - - globalSync.set("config", "permission", { ...map, [id]: nextValue }) - globalSync.updateConfig({ permission: { [id]: nextValue } }).catch(rollback) - } - - return ( -
-
-
-

{language.t("settings.permissions.title")}

-

{language.t("settings.permissions.description")}

-
-
- -
-
-

{language.t("settings.permissions.section.tools")}

-
- - {(item) => ( - - setText(e.currentTarget.value)} + onInput={(e) => setState("text", e.currentTarget.value)} style={{ width: "420px", "max-width": "100%", @@ -265,7 +276,7 @@ export const Playground = { max={1.5} step={0.01} value={visualDuration()} - onInput={(e) => setVisualDuration(Number(e.currentTarget.value))} + onInput={(e) => setState("visualDuration", Number(e.currentTarget.value))} /> {visualDuration().toFixed(2)}s
@@ -278,7 +289,7 @@ export const Playground = { max={0.5} step={0.01} value={bounce()} - onInput={(e) => setBounce(Number(e.currentTarget.value))} + onInput={(e) => setState("bounce", Number(e.currentTarget.value))} /> {bounce().toFixed(2)}
@@ -287,8 +298,14 @@ export const Playground = { fade ease
@@ -318,7 +335,7 @@ export const Playground = { max={14} step={0.5} value={blur()} - onInput={(e) => setBlur(Number(e.currentTarget.value))} + onInput={(e) => setState("blur", Number(e.currentTarget.value))} /> {blur()}px
diff --git a/packages/ui/src/components/text-reveal.stories.tsx b/packages/ui/src/components/text-reveal.stories.tsx index df514ca38d..af25d190c7 100644 --- a/packages/ui/src/components/text-reveal.stories.tsx +++ b/packages/ui/src/components/text-reveal.stories.tsx @@ -1,5 +1,6 @@ // @ts-nocheck -import { createSignal, onCleanup } from "solid-js" +import { onCleanup } from "solid-js" +import { createStore } from "solid-js/store" import { TextReveal } from "./text-reveal" export default { @@ -87,33 +88,42 @@ const headingSlot = { export const Playground = { render: () => { - const [index, setIndex] = createSignal(0) - const [cycling, setCycling] = createSignal(false) - const [growOnly, setGrowOnly] = createSignal(true) - - const [duration, setDuration] = createSignal(600) - const [bounce, setBounce] = createSignal(1.0) - const [bounceSoft, setBounceSoft] = createSignal(1.0) - - const [hybridTravel, setHybridTravel] = createSignal(25) - const [hybridEdge, setHybridEdge] = createSignal(17) - - const [edge, setEdge] = createSignal(17) - const [revealTravel, setRevealTravel] = createSignal(0) + const [state, setState] = createStore({ + index: 0, + cycling: false, + growOnly: true, + duration: 600, + bounce: 1.0, + bounceSoft: 1.0, + hybridTravel: 25, + hybridEdge: 17, + edge: 17, + revealTravel: 0, + }) + const index = () => state.index + const cycling = () => state.cycling + const growOnly = () => state.growOnly + const duration = () => state.duration + const bounce = () => state.bounce + const bounceSoft = () => state.bounceSoft + const hybridTravel = () => state.hybridTravel + const hybridEdge = () => state.hybridEdge + const edge = () => state.edge + const revealTravel = () => state.revealTravel let timer: number | undefined const text = () => TEXTS[index()] - const next = () => setIndex((i) => (i + 1) % TEXTS.length) - const prev = () => setIndex((i) => (i - 1 + TEXTS.length) % TEXTS.length) + const next = () => setState("index", (value) => (value + 1) % TEXTS.length) + const prev = () => setState("index", (value) => (value - 1 + TEXTS.length) % TEXTS.length) const toggleCycle = () => { if (cycling()) { if (timer) clearTimeout(timer) timer = undefined - setCycling(false) + setState("cycling", false) return } - setCycling(true) + setState("cycling", true) const tick = () => { next() timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600)) @@ -172,7 +182,7 @@ export const Playground = {
{TEXTS.map((t, i) => ( - ))} @@ -188,7 +198,7 @@ export const Playground = { -
@@ -204,7 +214,7 @@ export const Playground = { max="40" step="1" value={hybridEdge()} - onInput={(e) => setHybridEdge(e.currentTarget.valueAsNumber)} + onInput={(e) => setState("hybridEdge", e.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {hybridEdge()}% @@ -218,7 +228,7 @@ export const Playground = { max="40" step="1" value={hybridTravel()} - onInput={(e) => setHybridTravel(e.currentTarget.valueAsNumber)} + onInput={(e) => setState("hybridTravel", e.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {hybridTravel()}px @@ -234,7 +244,7 @@ export const Playground = { max="1400" step="10" value={duration()} - onInput={(e) => setDuration(e.currentTarget.valueAsNumber)} + onInput={(e) => setState("duration", e.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {duration()}ms @@ -248,7 +258,7 @@ export const Playground = { max="2" step="0.01" value={bounce()} - onInput={(e) => setBounce(e.currentTarget.valueAsNumber)} + onInput={(e) => setState("bounce", e.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {bounce().toFixed(2)} @@ -262,7 +272,7 @@ export const Playground = { max="1.5" step="0.01" value={bounceSoft()} - onInput={(e) => setBounceSoft(e.currentTarget.valueAsNumber)} + onInput={(e) => setState("bounceSoft", e.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {bounceSoft().toFixed(2)} @@ -280,7 +290,7 @@ export const Playground = { max="40" step="1" value={edge()} - onInput={(e) => setEdge(e.currentTarget.valueAsNumber)} + onInput={(e) => setState("edge", e.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {edge()}% @@ -294,7 +304,7 @@ export const Playground = { max="16" step="1" value={revealTravel()} - onInput={(e) => setRevealTravel(e.currentTarget.valueAsNumber)} + onInput={(e) => setState("revealTravel", e.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {revealTravel()}px diff --git a/packages/ui/src/components/text-reveal.tsx b/packages/ui/src/components/text-reveal.tsx index c4fe1302f0..02bf8084ce 100644 --- a/packages/ui/src/components/text-reveal.tsx +++ b/packages/ui/src/components/text-reveal.tsx @@ -1,4 +1,5 @@ -import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js" +import { createEffect, on, onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" const px = (value: number | string | undefined, fallback: number) => { if (typeof value === "number") return `${value}px` @@ -30,11 +31,18 @@ export function TextReveal(props: { growOnly?: boolean truncate?: boolean }) { - const [cur, setCur] = createSignal(props.text) - const [old, setOld] = createSignal() - const [width, setWidth] = createSignal("auto") - const [ready, setReady] = createSignal(false) - const [swapping, setSwapping] = createSignal(false) + const [state, setState] = createStore({ + cur: props.text, + old: undefined as string | undefined, + width: "auto", + ready: false, + swapping: false, + }) + const cur = () => state.cur + const old = () => state.old + const width = () => state.width + const ready = () => state.ready + const swapping = () => state.swapping let inRef: HTMLSpanElement | undefined let outRef: HTMLSpanElement | undefined let rootRef: HTMLSpanElement | undefined @@ -49,7 +57,7 @@ export function TextReveal(props: { const prev = Number.parseFloat(width()) if (Number.isFinite(prev) && next <= prev) return } - setWidth(`${next}px`) + setState("width", `${next}px`) } createEffect( @@ -58,25 +66,25 @@ export function TextReveal(props: { (next, prev) => { if (next === prev) return if (typeof next === "string" && typeof prev === "string" && next.startsWith(prev)) { - setCur(next) + setState("cur", next) widen(win()) return } - setSwapping(true) - setOld(prev) - setCur(next) + setState("swapping", true) + setState("old", prev) + setState("cur", next) if (typeof requestAnimationFrame !== "function") { widen(Math.max(win(), wout())) rootRef?.offsetHeight - setSwapping(false) + setState("swapping", false) return } if (frame !== undefined && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame) frame = requestAnimationFrame(() => { widen(Math.max(win(), wout())) rootRef?.offsetHeight - setSwapping(false) + setState("swapping", false) frame = undefined }) }, @@ -87,16 +95,16 @@ export function TextReveal(props: { widen(win()) const fonts = typeof document !== "undefined" ? document.fonts : undefined if (typeof requestAnimationFrame !== "function") { - setReady(true) + setState("ready", true) return } if (!fonts) { - requestAnimationFrame(() => setReady(true)) + requestAnimationFrame(() => setState("ready", true)) return } fonts.ready.finally(() => { widen(win()) - requestAnimationFrame(() => setReady(true)) + requestAnimationFrame(() => setState("ready", true)) }) }) diff --git a/packages/ui/src/components/text-strikethrough.stories.tsx b/packages/ui/src/components/text-strikethrough.stories.tsx index b07e745534..5e86413f93 100644 --- a/packages/ui/src/components/text-strikethrough.stories.tsx +++ b/packages/ui/src/components/text-strikethrough.stories.tsx @@ -1,5 +1,6 @@ // @ts-nocheck import { createEffect, createSignal, onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" import { useSpring } from "./motion-spring" import { TextStrikethrough } from "./text-strikethrough" @@ -130,12 +131,16 @@ function VariantF(props: { active: boolean; text: string }) { ) let baseRef: HTMLSpanElement | undefined let containerRef: HTMLSpanElement | undefined - const [textWidth, setTextWidth] = createSignal(0) - const [containerWidth, setContainerWidth] = createSignal(0) + const [state, setState] = createStore({ + textWidth: 0, + containerWidth: 0, + }) + const textWidth = () => state.textWidth + const containerWidth = () => state.containerWidth const measure = () => { - if (baseRef) setTextWidth(baseRef.scrollWidth) - if (containerRef) setContainerWidth(containerRef.offsetWidth) + if (baseRef) setState("textWidth", baseRef.scrollWidth) + if (containerRef) setState("containerWidth", containerRef.offsetWidth) } onMount(measure) diff --git a/packages/ui/src/components/text-strikethrough.tsx b/packages/ui/src/components/text-strikethrough.tsx index 211e7d44c0..aee5e0cbd0 100644 --- a/packages/ui/src/components/text-strikethrough.tsx +++ b/packages/ui/src/components/text-strikethrough.tsx @@ -1,5 +1,6 @@ import type { JSX } from "solid-js" -import { createEffect, createSignal, onCleanup, onMount } from "solid-js" +import { createEffect, onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" import { useSpring } from "./motion-spring" export function TextStrikethrough(props: { @@ -19,12 +20,16 @@ export function TextStrikethrough(props: { let baseRef: HTMLSpanElement | undefined let containerRef: HTMLSpanElement | undefined - const [textWidth, setTextWidth] = createSignal(0) - const [containerWidth, setContainerWidth] = createSignal(0) + const [state, setState] = createStore({ + textWidth: 0, + containerWidth: 0, + }) + const textWidth = () => state.textWidth + const containerWidth = () => state.containerWidth const measure = () => { - if (baseRef) setTextWidth(baseRef.scrollWidth) - if (containerRef) setContainerWidth(containerRef.offsetWidth) + if (baseRef) setState("textWidth", baseRef.scrollWidth) + if (containerRef) setState("containerWidth", containerRef.offsetWidth) } onMount(measure) diff --git a/packages/ui/src/components/thinking-heading.stories.tsx b/packages/ui/src/components/thinking-heading.stories.tsx index 90eb7ee319..3a65619ce1 100644 --- a/packages/ui/src/components/thinking-heading.stories.tsx +++ b/packages/ui/src/components/thinking-heading.stories.tsx @@ -1,5 +1,6 @@ // @ts-nocheck -import { createSignal, createEffect, on, onMount, onCleanup } from "solid-js" +import { createEffect, on, onMount, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" import { TextShimmer } from "./text-shimmer" import { TextReveal } from "./text-reveal" @@ -375,11 +376,18 @@ input[type="range"].heading-slider::-webkit-slider-thumb { // --------------------------------------------------------------------------- function AnimatedHeading(props) { - const [current, setCurrent] = createSignal(props.text) - const [leaving, setLeaving] = createSignal(undefined) - const [width, setWidth] = createSignal("auto") - const [ready, setReady] = createSignal(false) - const [swapping, setSwapping] = createSignal(false) + const [state, setState] = createStore({ + current: props.text, + leaving: undefined, + width: "auto", + ready: false, + swapping: false, + }) + const current = () => state.current + const leaving = () => state.leaving + const width = () => state.width + const ready = () => state.ready + const swapping = () => state.swapping let enterRef let leaveRef let containerRef @@ -391,16 +399,16 @@ function AnimatedHeading(props) { if (px <= 0) return const w = Number.parseFloat(width()) if (Number.isFinite(w) && px <= w) return - setWidth(`${px}px`) + setState("width", `${px}px`) } const measure = () => { if (!current()) { - setWidth("0px") + setState("width", "0px") return } const px = measureEnter() - if (px > 0) setWidth(`${px}px`) + if (px > 0) setState("width", `${px}px`) } createEffect( @@ -408,9 +416,9 @@ function AnimatedHeading(props) { () => props.text, (next, prev) => { if (next === prev) return - setSwapping(true) - setLeaving(prev) - setCurrent(next) + setState("swapping", true) + setState("leaving", prev) + setState("current", next) if (frame) cancelAnimationFrame(frame) frame = requestAnimationFrame(() => { @@ -420,10 +428,10 @@ function AnimatedHeading(props) { const leaveW = measureLeave() widen(Math.max(enterW, leaveW)) containerRef?.offsetHeight // reflow with max width + swap positions - setSwapping(false) + setState("swapping", false) } else { containerRef?.offsetHeight - setSwapping(false) + setState("swapping", false) measure() } frame = undefined @@ -436,7 +444,7 @@ function AnimatedHeading(props) { measure() document.fonts?.ready.finally(() => { measure() - requestAnimationFrame(() => setReady(true)) + requestAnimationFrame(() => setState("ready", true)) }) }) @@ -552,47 +560,56 @@ const VARIANTS: { key: string; label: string }[] = [] export const Playground = { render: () => { - const [heading, setHeading] = createSignal(HEADINGS[0]) - const [headingIndex, setHeadingIndex] = createSignal(0) - const [active, setActive] = createSignal(true) - const [cycling, setCycling] = createSignal(false) + const [state, setState] = createStore({ + heading: HEADINGS[0], + headingIndex: 0, + active: true, + cycling: false, + duration: 550, + blur: 2, + travel: 4, + bounce: 1.35, + maskSize: 12, + maskPad: 9, + maskHeight: 0, + debug: false, + odoBlur: false, + }) + const heading = () => state.heading + const headingIndex = () => state.headingIndex + const active = () => state.active + const cycling = () => state.cycling + const duration = () => state.duration + const blur = () => state.blur + const travel = () => state.travel + const bounce = () => state.bounce + const maskSize = () => state.maskSize + const maskPad = () => state.maskPad + const maskHeight = () => state.maskHeight + const debug = () => state.debug + const odoBlur = () => state.odoBlur let cycleTimer - // tunable params - const [duration, setDuration] = createSignal(550) - const [blur, setBlur] = createSignal(2) - const [travel, setTravel] = createSignal(4) - const [bounce, setBounce] = createSignal(1.35) - const [maskSize, setMaskSize] = createSignal(12) - const [maskPad, setMaskPad] = createSignal(9) - const [maskHeight, setMaskHeight] = createSignal(0) - const [debug, setDebug] = createSignal(false) - const [odoBlur, setOdoBlur] = createSignal(false) - const nextHeading = () => { - setHeadingIndex((i) => { - const next = (i + 1) % HEADINGS.length - setHeading(HEADINGS[next]) - return next - }) + const next = (headingIndex() + 1) % HEADINGS.length + setState("headingIndex", next) + setState("heading", HEADINGS[next]) } const prevHeading = () => { - setHeadingIndex((i) => { - const prev = (i - 1 + HEADINGS.length) % HEADINGS.length - setHeading(HEADINGS[prev]) - return prev - }) + const prev = (headingIndex() - 1 + HEADINGS.length) % HEADINGS.length + setState("headingIndex", prev) + setState("heading", HEADINGS[prev]) } const toggleCycling = () => { if (cycling()) { clearTimeout(cycleTimer) cycleTimer = undefined - setCycling(false) + setState("cycling", false) return } - setCycling(true) + setState("cycling", true) const tick = () => { if (!cycling()) return nextHeading() @@ -602,11 +619,11 @@ export const Playground = { } const clearHeading = () => { - setHeading(undefined) + setState("heading", undefined) if (cycling()) { clearTimeout(cycleTimer) cycleTimer = undefined - setCycling(false) + setState("cycling", false) } } @@ -686,7 +703,7 @@ export const Playground = { max={1400} step={50} value={duration()} - onInput={(e) => setDuration(Number(e.currentTarget.value))} + onInput={(e) => setState("duration", Number(e.currentTarget.value))} /> {duration()}ms
@@ -700,7 +717,7 @@ export const Playground = { max={16} step={0.5} value={blur()} - onInput={(e) => setBlur(Number(e.currentTarget.value))} + onInput={(e) => setState("blur", Number(e.currentTarget.value))} /> {blur()}px
@@ -714,7 +731,7 @@ export const Playground = { max={120} step={1} value={travel()} - onInput={(e) => setTravel(Number(e.currentTarget.value))} + onInput={(e) => setState("travel", Number(e.currentTarget.value))} /> {travel()}px
@@ -728,7 +745,7 @@ export const Playground = { max={2.2} step={0.05} value={bounce()} - onInput={(e) => setBounce(Number(e.currentTarget.value))} + onInput={(e) => setState("bounce", Number(e.currentTarget.value))} /> {bounce().toFixed(2)} {bounce() <= 1.05 ? "(none)" : bounce() >= 1.9 ? "(heavy)" : ""} @@ -744,7 +761,7 @@ export const Playground = { max={50} step={1} value={maskSize()} - onInput={(e) => setMaskSize(Number(e.currentTarget.value))} + onInput={(e) => setState("maskSize", Number(e.currentTarget.value))} /> {maskSize()}px {maskSize() === 0 ? "(hard)" : ""} @@ -760,7 +777,7 @@ export const Playground = { max={60} step={1} value={maskPad()} - onInput={(e) => setMaskPad(Number(e.currentTarget.value))} + onInput={(e) => setState("maskPad", Number(e.currentTarget.value))} /> {maskPad()}px
@@ -774,7 +791,7 @@ export const Playground = { max={80} step={1} value={maskHeight()} - onInput={(e) => setMaskHeight(Number(e.currentTarget.value))} + onInput={(e) => setState("maskHeight", Number(e.currentTarget.value))} /> {maskHeight()}px
@@ -795,13 +812,13 @@ export const Playground = { - - -
@@ -810,8 +827,8 @@ export const Playground = { {HEADINGS.map((h, i) => ( {[0, 1, 2, 3].map((value) => ( - ))} @@ -307,7 +328,7 @@ export const Playground = { max="1" step="0.01" value={dockOpenDuration()} - onInput={(event) => setDockOpenDuration(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("dockOpenDuration", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -324,7 +345,7 @@ export const Playground = { max="1" step="0.01" value={dockOpenBounce()} - onInput={(event) => setDockOpenBounce(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("dockOpenBounce", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -345,7 +366,7 @@ export const Playground = { max="1" step="0.01" value={dockCloseDuration()} - onInput={(event) => setDockCloseDuration(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("dockCloseDuration", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -362,7 +383,7 @@ export const Playground = { max="1" step="0.01" value={dockCloseBounce()} - onInput={(event) => setDockCloseBounce(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("dockCloseBounce", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -383,7 +404,7 @@ export const Playground = { max="1" step="0.01" value={drawerExpandDuration()} - onInput={(event) => setDrawerExpandDuration(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("drawerExpandDuration", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -400,7 +421,7 @@ export const Playground = { max="1" step="0.01" value={drawerExpandBounce()} - onInput={(event) => setDrawerExpandBounce(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("drawerExpandBounce", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -421,7 +442,7 @@ export const Playground = { max="1" step="0.01" value={drawerCollapseDuration()} - onInput={(event) => setDrawerCollapseDuration(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("drawerCollapseDuration", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -438,7 +459,7 @@ export const Playground = { max="1" step="0.01" value={drawerCollapseBounce()} - onInput={(event) => setDrawerCollapseBounce(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("drawerCollapseBounce", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -459,7 +480,7 @@ export const Playground = { max="1400" step="10" value={subtitleDuration()} - onInput={(event) => setSubtitleDuration(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("subtitleDuration", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -473,7 +494,7 @@ export const Playground = { setSubtitleAuto(event.currentTarget.checked)} + onInput={(event) => setCfg("subtitleAuto", event.currentTarget.checked)} /> {subtitleAuto() ? "on" : "off"} @@ -489,7 +510,7 @@ export const Playground = { max="40" step="1" value={subtitleTravel()} - onInput={(event) => setSubtitleTravel(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("subtitleTravel", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {subtitleTravel()}px @@ -504,7 +525,7 @@ export const Playground = { max="40" step="1" value={subtitleEdge()} - onInput={(event) => setSubtitleEdge(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("subtitleEdge", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {subtitleEdge()}% @@ -523,7 +544,7 @@ export const Playground = { max="1400" step="10" value={countDuration()} - onInput={(event) => setCountDuration(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("countDuration", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> @@ -540,7 +561,7 @@ export const Playground = { max="40" step="1" value={countMask()} - onInput={(event) => setCountMask(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("countMask", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {countMask()}% @@ -555,7 +576,7 @@ export const Playground = { max="14" step="1" value={countMaskHeight()} - onInput={(event) => setCountMaskHeight(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("countMaskHeight", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> {countMaskHeight()}px @@ -570,7 +591,7 @@ export const Playground = { max="1200" step="10" value={countWidthDuration()} - onInput={(event) => setCountWidthDuration(event.currentTarget.valueAsNumber)} + onInput={(event) => setCfg("countWidthDuration", event.currentTarget.valueAsNumber)} style={{ flex: 1 }} /> diff --git a/packages/ui/src/components/tool-count-summary.stories.tsx b/packages/ui/src/components/tool-count-summary.stories.tsx index 4be3a02bbe..cf160b188b 100644 --- a/packages/ui/src/components/tool-count-summary.stories.tsx +++ b/packages/ui/src/components/tool-count-summary.stories.tsx @@ -1,5 +1,6 @@ // @ts-nocheck -import { createSignal, onCleanup } from "solid-js" +import { onCleanup } from "solid-js" +import { createStore } from "solid-js/store" import { AnimatedCountList, type CountItem } from "./tool-count-summary" import { ToolStatusTitle } from "./tool-status-title" @@ -57,11 +58,18 @@ const smallBtn = (active?: boolean) => export const Playground = { render: () => { - const [reads, setReads] = createSignal(0) - const [searches, setSearches] = createSignal(0) - const [lists, setLists] = createSignal(0) - const [active, setActive] = createSignal(false) - const [reducedMotion, setReducedMotion] = createSignal(false) + const [state, setState] = createStore({ + reads: 0, + searches: 0, + lists: 0, + active: false, + reducedMotion: false, + }) + const reads = () => state.reads + const searches = () => state.searches + const lists = () => state.lists + const active = () => state.active + const reducedMotion = () => state.reducedMotion let timeouts: ReturnType[] = [] @@ -74,10 +82,10 @@ export const Playground = { const startSim = () => { clearAll() - setReads(0) - setSearches(0) - setLists(0) - setActive(true) + setState("reads", 0) + setState("searches", 0) + setState("lists", 0) + setState("active", true) const steps = rand(3, 10) let elapsed = 0 @@ -86,27 +94,27 @@ export const Playground = { elapsed += delay const t = setTimeout(() => { const pick = rand(0, 2) - if (pick === 0) setReads((n) => n + 1) - else if (pick === 1) setSearches((n) => n + 1) - else setLists((n) => n + 1) + if (pick === 0) setState("reads", (value) => value + 1) + else if (pick === 1) setState("searches", (value) => value + 1) + else setState("lists", (value) => value + 1) }, elapsed) timeouts.push(t) } - const end = setTimeout(() => setActive(false), elapsed + 100) + const end = setTimeout(() => setState("active", false), elapsed + 100) timeouts.push(end) } const stopSim = () => { clearAll() - setActive(false) + setState("active", false) } const reset = () => { stopSim() - setReads(0) - setSearches(0) - setLists(0) + setState("reads", 0) + setState("searches", 0) + setState("lists", 0) } const items = (): CountItem[] => [ @@ -164,19 +172,19 @@ export const Playground = { -
- - -
diff --git a/packages/ui/src/components/tool-error-card.tsx b/packages/ui/src/components/tool-error-card.tsx index ba39ae586a..0c99924de9 100644 --- a/packages/ui/src/components/tool-error-card.tsx +++ b/packages/ui/src/components/tool-error-card.tsx @@ -1,4 +1,5 @@ -import { type ComponentProps, createMemo, createSignal, Show, splitProps } from "solid-js" +import { type ComponentProps, createMemo, Show, splitProps } from "solid-js" +import { createStore } from "solid-js/store" import { Card, CardDescription } from "./card" import { Collapsible } from "./collapsible" import { Icon } from "./icon" @@ -16,8 +17,12 @@ export interface ToolErrorCardProps extends Omit, "c export function ToolErrorCard(props: ToolErrorCardProps) { const i18n = useI18n() - const [open, setOpen] = createSignal(props.defaultOpen ?? false) - const [copied, setCopied] = createSignal(false) + const [state, setState] = createStore({ + open: props.defaultOpen ?? false, + copied: false, + }) + const open = () => state.open + const copied = () => state.copied const [split, rest] = splitProps(props, ["tool", "error", "defaultOpen", "subtitle", "href"]) const name = createMemo(() => { const map: Record = { @@ -65,13 +70,18 @@ export function ToolErrorCard(props: ToolErrorCardProps) { const text = cleaned() if (!text) return await navigator.clipboard.writeText(text) - setCopied(true) - setTimeout(() => setCopied(false), 2000) + setState("copied", true) + setTimeout(() => setState("copied", false), 2000) } return ( - + setState("open", value)} + >
diff --git a/packages/ui/src/components/tool-status-title.tsx b/packages/ui/src/components/tool-status-title.tsx index 68440b6c63..2a58e0e5bb 100644 --- a/packages/ui/src/components/tool-status-title.tsx +++ b/packages/ui/src/components/tool-status-title.tsx @@ -1,4 +1,5 @@ -import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js" +import { Show, createEffect, createMemo, on, onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" import { TextShimmer } from "./text-shimmer" function common(active: string, done: string) { @@ -35,8 +36,12 @@ export function ToolStatusTitle(props: { const activeTail = createMemo(() => (suffix() ? split().active : props.activeText)) const doneTail = createMemo(() => (suffix() ? split().done : props.doneText)) - const [width, setWidth] = createSignal("auto") - const [ready, setReady] = createSignal(false) + const [state, setState] = createStore({ + width: "auto", + ready: false, + }) + const width = () => state.width + const ready = () => state.ready let activeRef: HTMLSpanElement | undefined let doneRef: HTMLSpanElement | undefined let frame: number | undefined @@ -45,7 +50,7 @@ export function ToolStatusTitle(props: { const measure = () => { const target = props.active ? activeRef : doneRef const px = contentWidth(target) - if (px > 0) setWidth(`${px}px`) + if (px > 0) setState("width", `${px}px`) } const schedule = () => { @@ -62,13 +67,13 @@ export function ToolStatusTitle(props: { const finish = () => { if (typeof requestAnimationFrame !== "function") { - setReady(true) + setState("ready", true) return } if (readyFrame !== undefined) cancelAnimationFrame(readyFrame) readyFrame = requestAnimationFrame(() => { readyFrame = undefined - setReady(true) + setState("ready", true) }) } diff --git a/packages/ui/src/pierre/file-find.ts b/packages/ui/src/pierre/file-find.ts index ee608152d5..692ab31670 100644 --- a/packages/ui/src/pierre/file-find.ts +++ b/packages/ui/src/pierre/file-find.ts @@ -1,4 +1,5 @@ -import { createEffect, createSignal, onCleanup, onMount } from "solid-js" +import { createEffect, onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" export type FindHost = { element: () => HTMLElement | undefined @@ -107,11 +108,18 @@ export function createFileFind(opts: CreateFileFindOptions) { let mode: "highlights" | "overlay" = "overlay" let hits: Range[] = [] - const [open, setOpen] = createSignal(false) - const [query, setQuery] = createSignal("") - const [index, setIndex] = createSignal(0) - const [count, setCount] = createSignal(0) - const [pos, setPos] = createSignal({ top: 8, right: 8 }) + const [state, setState] = createStore({ + open: false, + query: "", + index: 0, + count: 0, + pos: { top: 8, right: 8 }, + }) + const open = () => state.open + const query = () => state.query + const index = () => state.index + const count = () => state.count + const pos = () => state.pos const clearOverlayScroll = () => { for (const el of overlayScroll) el.removeEventListener("scroll", scheduleOverlay) @@ -200,8 +208,8 @@ export function createFileFind(opts: CreateFileFindOptions) { clearOverlay() clearOverlayScroll() hits = [] - setCount(0) - setIndex(0) + setState("count", 0) + setState("index", 0) } const positionBar = () => { @@ -214,7 +222,7 @@ export function createFileFind(opts: CreateFileFindOptions) { const title = parseFloat(getComputedStyle(root).getPropertyValue("--session-title-height")) const header = Number.isNaN(title) ? 0 : title - setPos({ + setState("pos", { top: Math.round(rect.top) + header - 4, right: Math.round(window.innerWidth - rect.right) + 8, }) @@ -318,8 +326,8 @@ export function createFileFind(opts: CreateFileFindOptions) { const currentIndex = total ? Math.min(desired, total - 1) : 0 hits = ranges - setCount(total) - setIndex(currentIndex) + setState("count", total) + setState("index", currentIndex) const active = ranges[currentIndex] if (mode === "highlights") { @@ -342,8 +350,8 @@ export function createFileFind(opts: CreateFileFindOptions) { } const close = () => { - setOpen(false) - setQuery("") + setState("open", false) + setState("query", "") clearFind() if (current === host) current = undefined } @@ -352,7 +360,7 @@ export function createFileFind(opts: CreateFileFindOptions) { if (current && current !== host) current.close() current = host target = host - if (!open()) setOpen(true) + if (!open()) setState("open", true) requestAnimationFrame(() => { apply({ scroll: true }) input?.focus() @@ -366,7 +374,7 @@ export function createFileFind(opts: CreateFileFindOptions) { if (total <= 0) return const currentIndex = (index() + dir + total) % total - setIndex(currentIndex) + setState("index", currentIndex) const active = hits[currentIndex] if (!active) return @@ -449,8 +457,8 @@ export function createFileFind(opts: CreateFileFindOptions) { input = el }, setQuery: (value: string) => { - setQuery(value) - setIndex(0) + setState("query", value) + setState("index", 0) apply({ reset: true, scroll: true }) }, focus, From 05cb3c87ca387be41aceb5ccad978c6848a56f70 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 13 Mar 2026 06:48:38 -0500 Subject: [PATCH 187/200] chore(app): i18n sync (#17283) --- packages/app/src/app.tsx | 15 +++- packages/app/src/components/debug-bar.tsx | 70 +++++++++-------- .../app/src/components/dialog-select-file.tsx | 2 +- .../src/components/dialog-select-server.tsx | 4 +- .../app/src/components/server/server-row.tsx | 4 +- .../src/components/session/session-header.tsx | 54 ++++++------- .../session/session-sortable-terminal-tab.tsx | 7 +- .../app/src/components/settings-keybinds.tsx | 2 +- packages/app/src/components/terminal.tsx | 2 +- packages/app/src/context/command.tsx | 61 ++++++++++++--- packages/app/src/context/file.tsx | 6 +- packages/app/src/context/global-sdk.tsx | 6 +- packages/app/src/context/global-sync.tsx | 1 + .../app/src/context/global-sync/bootstrap.ts | 2 +- .../context/global-sync/child-store.test.ts | 1 + .../src/context/global-sync/child-store.ts | 9 ++- packages/app/src/context/terminal-title.ts | 51 ++++++++++++ packages/app/src/context/terminal.tsx | 11 +-- packages/app/src/i18n/ar.ts | 73 ++++++++++++++++++ packages/app/src/i18n/br.ts | 75 ++++++++++++++++++ packages/app/src/i18n/bs.ts | 75 ++++++++++++++++++ packages/app/src/i18n/da.ts | 75 ++++++++++++++++++ packages/app/src/i18n/de.ts | 76 ++++++++++++++++++ packages/app/src/i18n/en.ts | 76 ++++++++++++++++++ packages/app/src/i18n/es.ts | 75 ++++++++++++++++++ packages/app/src/i18n/fr.ts | 77 +++++++++++++++++++ packages/app/src/i18n/ja.ts | 74 ++++++++++++++++++ packages/app/src/i18n/ko.ts | 74 ++++++++++++++++++ packages/app/src/i18n/no.ts | 75 ++++++++++++++++++ packages/app/src/i18n/pl.ts | 76 ++++++++++++++++++ packages/app/src/i18n/ru.ts | 75 ++++++++++++++++++ packages/app/src/i18n/th.ts | 75 ++++++++++++++++++ packages/app/src/i18n/tr.ts | 74 ++++++++++++++++++ packages/app/src/i18n/zh.ts | 73 ++++++++++++++++++ packages/app/src/i18n/zht.ts | 73 ++++++++++++++++++ packages/app/src/pages/error.tsx | 32 ++++---- packages/app/src/pages/layout.tsx | 2 +- packages/app/src/pages/session.tsx | 8 +- .../composer/session-composer-region.tsx | 1 - .../composer/session-question-dock.tsx | 2 +- .../session/composer/session-todo-dock.tsx | 28 +++++-- .../app/src/pages/session/terminal-label.ts | 6 +- packages/ui/src/components/basic-tool.tsx | 5 +- packages/ui/src/components/file-search.tsx | 11 ++- .../components/line-comment-annotations.tsx | 8 +- packages/ui/src/components/message-part.tsx | 20 ++--- .../ui/src/components/tool-error-card.tsx | 16 ++-- packages/ui/src/i18n/ar.ts | 12 +++ packages/ui/src/i18n/br.ts | 12 +++ packages/ui/src/i18n/bs.ts | 12 +++ packages/ui/src/i18n/da.ts | 12 +++ packages/ui/src/i18n/de.ts | 12 +++ packages/ui/src/i18n/en.ts | 13 ++++ packages/ui/src/i18n/es.ts | 12 +++ packages/ui/src/i18n/fr.ts | 12 +++ packages/ui/src/i18n/ja.ts | 12 +++ packages/ui/src/i18n/ko.ts | 12 +++ packages/ui/src/i18n/no.ts | 12 +++ packages/ui/src/i18n/pl.ts | 12 +++ packages/ui/src/i18n/ru.ts | 12 +++ packages/ui/src/i18n/th.ts | 12 +++ packages/ui/src/i18n/tr.ts | 12 +++ packages/ui/src/i18n/zh.ts | 12 +++ packages/ui/src/i18n/zht.ts | 12 +++ packages/ui/src/pierre/selection-bridge.ts | 9 ++- 65 files changed, 1776 insertions(+), 156 deletions(-) create mode 100644 packages/app/src/context/terminal-title.ts diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index c6fca36d59..e370862212 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -12,6 +12,7 @@ import { type BaseRouterProps, Navigate, Route, Router } from "@solidjs/router" import { type Duration, Effect } from "effect" import { type Component, + createMemo, createResource, createSignal, ErrorBoundary, @@ -67,7 +68,7 @@ const SessionIndexRoute = () => function UiI18nBridge(props: ParentProps) { const language = useLanguage() - return {props.children} + return {props.children} } declare global { @@ -218,8 +219,12 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { } function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: ServerConnection.Key) => void }) { + const language = useLanguage() const server = useServer() const others = () => server.list.filter((s) => ServerConnection.key(s) !== server.key) + const name = createMemo(() => server.name || server.key) + const serverToken = "\u0000server\u0000" + const unreachable = createMemo(() => language.t("app.server.unreachable", { server: serverToken }).split(serverToken)) const timer = setInterval(() => props.onRetry?.(), 1000) onCleanup(() => clearInterval(timer)) @@ -229,13 +234,15 @@ function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key:

- Could not reach {server.name || server.key} + {unreachable()[0]} + {name()} + {unreachable()[1]}

-

Retrying automatically...

+

{language.t("app.server.retrying")}

0}>
- Other servers + {language.t("app.server.otherServers")}
{(conn) => { diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index 6fde71f3b2..cbb24f77bc 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -2,6 +2,7 @@ import { useIsRouting, useLocation } from "@solidjs/router" import { batch, createEffect, onCleanup, onMount } from "solid-js" import { createStore } from "solid-js/store" import { Tooltip } from "@opencode-ai/ui/tooltip" +import { useLanguage } from "@/context/language" type Mem = Performance & { memory?: { @@ -27,17 +28,17 @@ type Obs = PerformanceObserverInit & { const span = 5000 const ms = (n?: number, d = 0) => { - if (n === undefined || Number.isNaN(n)) return "n/a" + if (n === undefined || Number.isNaN(n)) return return `${n.toFixed(d)}ms` } const time = (n?: number) => { - if (n === undefined || Number.isNaN(n)) return "n/a" + if (n === undefined || Number.isNaN(n)) return return `${Math.round(n)}` } const mb = (n?: number) => { - if (n === undefined || Number.isNaN(n)) return "n/a" + if (n === undefined || Number.isNaN(n)) return const v = n / 1024 / 1024 return `${v >= 1024 ? v.toFixed(0) : v.toFixed(1)}MB` } @@ -74,6 +75,7 @@ function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; } export function DebugBar() { + const language = useLanguage() const location = useLocation() const routing = useIsRouting() const [state, setState] = createStore({ @@ -98,14 +100,15 @@ export function DebugBar() { }, }) + const na = () => language.t("debugBar.na") const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined) const heapv = () => { const value = heap() - if (value === undefined) return "n/a" + if (value === undefined) return na() return `${Math.round(value * 100)}%` } - const longv = () => (state.long.count === undefined ? "n/a" : `${time(state.long.block)}/${state.long.count}`) - const navv = () => (state.nav.pending ? "..." : time(state.nav.dur)) + const longv = () => (state.long.count === undefined ? na() : `${time(state.long.block) ?? na()}/${state.long.count}`) + const navv = () => (state.nav.pending ? "..." : (time(state.nav.dur) ?? na())) let prev = "" let start = 0 @@ -359,7 +362,7 @@ export function DebugBar() { return (
diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index 655aba0b02..eb039c14d6 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -149,7 +149,7 @@ function ServerForm(props: ServerFormProps) { {conn().http.username} ) : ( - no username + {language.t("server.row.noUsername")} )} {conn().http.password && ••••••••} diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 8cb704bf1d..ae9d2800ed 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -46,63 +46,63 @@ type OS = "macos" | "windows" | "linux" | "unknown" const MAC_APPS = [ { id: "vscode", - label: "VS Code", + label: "session.header.open.app.vscode", icon: "vscode", openWith: "Visual Studio Code", }, - { id: "cursor", label: "Cursor", icon: "cursor", openWith: "Cursor" }, - { id: "zed", label: "Zed", icon: "zed", openWith: "Zed" }, - { id: "textmate", label: "TextMate", icon: "textmate", openWith: "TextMate" }, + { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "Cursor" }, + { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "Zed" }, + { id: "textmate", label: "session.header.open.app.textmate", icon: "textmate", openWith: "TextMate" }, { id: "antigravity", - label: "Antigravity", + label: "session.header.open.app.antigravity", icon: "antigravity", openWith: "Antigravity", }, - { id: "terminal", label: "Terminal", icon: "terminal", openWith: "Terminal" }, - { id: "iterm2", label: "iTerm2", icon: "iterm2", openWith: "iTerm" }, - { id: "ghostty", label: "Ghostty", icon: "ghostty", openWith: "Ghostty" }, - { id: "warp", label: "Warp", icon: "warp", openWith: "Warp" }, - { id: "xcode", label: "Xcode", icon: "xcode", openWith: "Xcode" }, + { id: "terminal", label: "session.header.open.app.terminal", icon: "terminal", openWith: "Terminal" }, + { id: "iterm2", label: "session.header.open.app.iterm2", icon: "iterm2", openWith: "iTerm" }, + { id: "ghostty", label: "session.header.open.app.ghostty", icon: "ghostty", openWith: "Ghostty" }, + { id: "warp", label: "session.header.open.app.warp", icon: "warp", openWith: "Warp" }, + { id: "xcode", label: "session.header.open.app.xcode", icon: "xcode", openWith: "Xcode" }, { id: "android-studio", - label: "Android Studio", + label: "session.header.open.app.androidStudio", icon: "android-studio", openWith: "Android Studio", }, { id: "sublime-text", - label: "Sublime Text", + label: "session.header.open.app.sublimeText", icon: "sublime-text", openWith: "Sublime Text", }, ] as const const WINDOWS_APPS = [ - { id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" }, - { id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" }, - { id: "zed", label: "Zed", icon: "zed", openWith: "zed" }, + { id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" }, + { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" }, + { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" }, { id: "powershell", - label: "PowerShell", + label: "session.header.open.app.powershell", icon: "powershell", openWith: "powershell", }, { id: "sublime-text", - label: "Sublime Text", + label: "session.header.open.app.sublimeText", icon: "sublime-text", openWith: "Sublime Text", }, ] as const const LINUX_APPS = [ - { id: "vscode", label: "VS Code", icon: "vscode", openWith: "code" }, - { id: "cursor", label: "Cursor", icon: "cursor", openWith: "cursor" }, - { id: "zed", label: "Zed", icon: "zed", openWith: "zed" }, + { id: "vscode", label: "session.header.open.app.vscode", icon: "vscode", openWith: "code" }, + { id: "cursor", label: "session.header.open.app.cursor", icon: "cursor", openWith: "cursor" }, + { id: "zed", label: "session.header.open.app.zed", icon: "zed", openWith: "zed" }, { id: "sublime-text", - label: "Sublime Text", + label: "session.header.open.app.sublimeText", icon: "sublime-text", openWith: "Sublime Text", }, @@ -160,9 +160,9 @@ export function SessionHeader() { }) const fileManager = createMemo(() => { - if (os() === "macos") return { label: "Finder", icon: "finder" as const } - if (os() === "windows") return { label: "File Explorer", icon: "file-explorer" as const } - return { label: "File Manager", icon: "finder" as const } + if (os() === "macos") return { label: "session.header.open.finder", icon: "finder" as const } + if (os() === "windows") return { label: "session.header.open.fileExplorer", icon: "file-explorer" as const } + return { label: "session.header.open.fileManager", icon: "finder" as const } }) createEffect(() => { @@ -187,8 +187,10 @@ export function SessionHeader() { const options = createMemo(() => { return [ - { id: "finder", label: fileManager().label, icon: fileManager().icon }, - ...apps().filter((app) => exists[app.id]), + { id: "finder", label: language.t(fileManager().label), icon: fileManager().icon }, + ...apps() + .filter((app) => exists[app.id]) + .map((app) => ({ ...app, label: language.t(app.label) })), ] as const }) diff --git a/packages/app/src/components/session/session-sortable-terminal-tab.tsx b/packages/app/src/components/session/session-sortable-terminal-tab.tsx index 4f49911c12..8989587425 100644 --- a/packages/app/src/components/session/session-sortable-terminal-tab.tsx +++ b/packages/app/src/components/session/session-sortable-terminal-tab.tsx @@ -6,6 +6,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { Tabs } from "@opencode-ai/ui/tabs" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Icon } from "@opencode-ai/ui/icon" +import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title" import { useTerminal, type LocalPTY } from "@/context/terminal" import { useLanguage } from "@/context/language" import { focusTerminalById } from "@/pages/session/helpers" @@ -27,11 +28,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => const isDefaultTitle = () => { const number = props.terminal.titleNumber if (!Number.isFinite(number) || number <= 0) return false - const match = props.terminal.title.match(/^Terminal (\d+)$/) - if (!match) return false - const parsed = Number(match[1]) - if (!Number.isFinite(parsed) || parsed <= 0) return false - return parsed === number + return isDefaultTerminalTitle(props.terminal.title, number) } const label = () => { diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index 1e42447895..7e2a48110c 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -239,7 +239,7 @@ function useKeyCapture(input: { showToast({ title: input.language.t("settings.shortcuts.conflict.title"), description: input.language.t("settings.shortcuts.conflict.description", { - keybind: formatKeybind(next), + keybind: formatKeybind(next, input.language.t), titles: [...conflicts.values()].join(", "), }), }) diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index 8409032938..ff455ebe20 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -519,7 +519,7 @@ export const Terminal = (props: TerminalProps) => { if (event.code !== 1000) { if (once.value) return once.value = true - local.onConnectError?.(new Error(`WebSocket closed abnormally: ${event.code}`)) + local.onConnectError?.(new Error(language.t("terminal.connectionLost.abnormalClose", { code: event.code }))) } } socket.addEventListener("close", handleClose) diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx index 03bd6318da..2c6799d12b 100644 --- a/packages/app/src/context/command.tsx +++ b/packages/app/src/context/command.tsx @@ -2,6 +2,7 @@ import { createEffect, createMemo, onCleanup, onMount, type Accessor } from "sol import { createStore } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { useDialog } from "@opencode-ai/ui/context/dialog" +import { dict as en } from "@/i18n/en" import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" import { Persist, persisted } from "@/utils/persist" @@ -13,6 +14,27 @@ const DEFAULT_PALETTE_KEYBIND = "mod+shift+p" const SUGGESTED_PREFIX = "suggested." const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new", "file.attach"]) +type KeyLabel = + | "common.key.ctrl" + | "common.key.alt" + | "common.key.shift" + | "common.key.meta" + | "common.key.space" + | "common.key.backspace" + | "common.key.enter" + | "common.key.tab" + | "common.key.delete" + | "common.key.home" + | "common.key.end" + | "common.key.pageUp" + | "common.key.pageDown" + | "common.key.insert" + | "common.key.esc" + +function keyText(key: KeyLabel, t?: (key: KeyLabel) => string) { + return t ? t(key) : en[key] +} + function actionId(id: string) { if (!id.startsWith(SUGGESTED_PREFIX)) return id return id.slice(SUGGESTED_PREFIX.length) @@ -145,7 +167,7 @@ export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean return false } -export function formatKeybind(config: string): string { +export function formatKeybind(config: string, t?: (key: KeyLabel) => string): string { if (!config || config === "none") return "" const keybinds = parseKeybind(config) @@ -154,10 +176,10 @@ export function formatKeybind(config: string): string { const kb = keybinds[0] const parts: string[] = [] - if (kb.ctrl) parts.push(IS_MAC ? "⌃" : "Ctrl") - if (kb.alt) parts.push(IS_MAC ? "⌥" : "Alt") - if (kb.shift) parts.push(IS_MAC ? "⇧" : "Shift") - if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta") + if (kb.ctrl) parts.push(IS_MAC ? "⌃" : keyText("common.key.ctrl", t)) + if (kb.alt) parts.push(IS_MAC ? "⌥" : keyText("common.key.alt", t)) + if (kb.shift) parts.push(IS_MAC ? "⇧" : keyText("common.key.shift", t)) + if (kb.meta) parts.push(IS_MAC ? "⌘" : keyText("common.key.meta", t)) if (kb.key) { const keys: Record = { @@ -167,10 +189,29 @@ export function formatKeybind(config: string): string { arrowright: "→", comma: ",", plus: "+", - space: "Space", + } + const named: Record = { + backspace: "common.key.backspace", + delete: "common.key.delete", + end: "common.key.end", + enter: "common.key.enter", + esc: "common.key.esc", + escape: "common.key.esc", + home: "common.key.home", + insert: "common.key.insert", + pagedown: "common.key.pageDown", + pageup: "common.key.pageUp", + space: "common.key.space", + tab: "common.key.tab", } const key = kb.key.toLowerCase() - const displayKey = keys[key] ?? (key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1)) + const displayKey = + keys[key] ?? + (named[key] + ? keyText(named[key], t) + : key.length === 1 + ? key.toUpperCase() + : key.charAt(0).toUpperCase() + key.slice(1)) parts.push(displayKey) } @@ -364,17 +405,17 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex }, keybind(id: string) { if (id === PALETTE_ID) { - return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND) + return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND, language.t) } const base = actionId(id) const option = options().find((x) => actionId(x.id) === base) - if (option?.keybind) return formatKeybind(option.keybind) + if (option?.keybind) return formatKeybind(option.keybind, language.t) const meta = catalog[base] const config = bind(base, meta?.keybind) if (!config) return "" - return formatKeybind(config) + return formatKeybind(config, language.t) }, show: showPalette, keybinds(enabled: boolean) { diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx index 99c6d2e421..f8fec7142d 100644 --- a/packages/app/src/context/file.tsx +++ b/packages/app/src/context/file.tsx @@ -43,10 +43,10 @@ export { touchFileContent, } -function errorMessage(error: unknown) { +function errorMessage(error: unknown, fallback: string) { if (error instanceof Error && error.message) return error.message if (typeof error === "string" && error) return error - return "Unknown error" + return fallback } export const { use: useFile, provider: FileProvider } = createSimpleContext({ @@ -184,7 +184,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ }) .catch((e) => { if (scope() !== directory) return - setLoadError(file, errorMessage(e)) + setLoadError(file, errorMessage(e, language.t("error.chain.unknown"))) }) .finally(() => { inflight.delete(key) diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx index c1a87b95b8..60e9fd6d54 100644 --- a/packages/app/src/context/global-sdk.tsx +++ b/packages/app/src/context/global-sdk.tsx @@ -4,6 +4,7 @@ import { createGlobalEmitter } from "@solid-primitives/event-bus" import { batch, onCleanup } from "solid-js" import z from "zod" import { createSdkForServer } from "@/utils/server" +import { useLanguage } from "./language" import { usePlatform } from "./platform" import { useServer } from "./server" @@ -14,6 +15,7 @@ const abortError = z.object({ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({ name: "GlobalSDK", init: () => { + const language = useLanguage() const server = useServer() const platform = usePlatform() const abort = new AbortController() @@ -30,7 +32,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo })() const currentServer = server.current - if (!currentServer) throw new Error("No server available") + if (!currentServer) throw new Error(language.t("error.globalSDK.noServerAvailable")) const eventSdk = createSdkForServer({ signal: abort.signal, @@ -218,7 +220,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo event: emitter, createClient(opts: Omit[0], "server" | "fetch">) { const s = server.current - if (!s) throw new Error("Server not available") + if (!s) throw new Error(language.t("error.globalSDK.serverNotAvailable")) return createSdkForServer({ server: s.http, fetch: platform.fetch, diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 1b6cdf530a..c840988692 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -164,6 +164,7 @@ function createGlobalSync() { sdkCache.delete(directory) clearSessionPrefetchDirectory(directory) }, + translate: language.t, }) const sdkFor = (directory: string) => { diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index 8b1a3c48c5..13494b7ade 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -139,7 +139,7 @@ export async function bootstrapDirectory(input: { const project = getFilename(input.directory) showToast({ variant: "error", - title: `Failed to reload ${project}`, + title: input.translate("toast.project.reloadFailed.title", { project }), description: formatServerError(err, input.translate), }) input.setStore("status", "partial") diff --git a/packages/app/src/context/global-sync/child-store.test.ts b/packages/app/src/context/global-sync/child-store.test.ts index cec76ff87e..eee763f16d 100644 --- a/packages/app/src/context/global-sync/child-store.test.ts +++ b/packages/app/src/context/global-sync/child-store.test.ts @@ -21,6 +21,7 @@ describe("createChildStoreManager", () => { isLoadingSessions: () => false, onBootstrap() {}, onDispose() {}, + translate: (key) => key, }) Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => { diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index e2ada244fb..d5904c6096 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -21,6 +21,7 @@ export function createChildStoreManager(input: { isLoadingSessions: (directory: string) => boolean onBootstrap: (directory: string) => void onDispose: (directory: string) => void + translate: (key: string, vars?: Record) => string }) { const children: Record, SetStoreFunction]> = {} const vcsCache = new Map() @@ -129,7 +130,7 @@ export function createChildStoreManager(input: { createStore({ value: undefined as VcsInfo | undefined }), ), ) - if (!vcs) throw new Error("Failed to create persisted cache") + if (!vcs) throw new Error(input.translate("error.childStore.persistedCacheCreateFailed")) const vcsStore = vcs[0] vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcs[3] }) @@ -139,7 +140,7 @@ export function createChildStoreManager(input: { createStore({ value: undefined as ProjectMeta | undefined }), ), ) - if (!meta) throw new Error("Failed to create persisted project metadata") + if (!meta) throw new Error(input.translate("error.childStore.persistedProjectMetadataCreateFailed")) metaCache.set(directory, { store: meta[0], setStore: meta[1], ready: meta[3] }) const icon = runWithOwner(input.owner, () => @@ -148,7 +149,7 @@ export function createChildStoreManager(input: { createStore({ value: undefined as string | undefined }), ), ) - if (!icon) throw new Error("Failed to create persisted project icon") + if (!icon) throw new Error(input.translate("error.childStore.persistedProjectIconCreateFailed")) iconCache.set(directory, { store: icon[0], setStore: icon[1], ready: icon[3] }) const init = () => @@ -211,7 +212,7 @@ export function createChildStoreManager(input: { } mark(directory) const childStore = children[directory] - if (!childStore) throw new Error("Failed to create store") + if (!childStore) throw new Error(input.translate("error.childStore.storeCreateFailed")) return childStore } diff --git a/packages/app/src/context/terminal-title.ts b/packages/app/src/context/terminal-title.ts new file mode 100644 index 0000000000..3e8fa9af25 --- /dev/null +++ b/packages/app/src/context/terminal-title.ts @@ -0,0 +1,51 @@ +import { dict as ar } from "@/i18n/ar" +import { dict as br } from "@/i18n/br" +import { dict as bs } from "@/i18n/bs" +import { dict as da } from "@/i18n/da" +import { dict as de } from "@/i18n/de" +import { dict as en } from "@/i18n/en" +import { dict as es } from "@/i18n/es" +import { dict as fr } from "@/i18n/fr" +import { dict as ja } from "@/i18n/ja" +import { dict as ko } from "@/i18n/ko" +import { dict as no } from "@/i18n/no" +import { dict as pl } from "@/i18n/pl" +import { dict as ru } from "@/i18n/ru" +import { dict as th } from "@/i18n/th" +import { dict as tr } from "@/i18n/tr" +import { dict as zh } from "@/i18n/zh" +import { dict as zht } from "@/i18n/zht" + +const numbered = Array.from( + new Set([ + en["terminal.title.numbered"], + ar["terminal.title.numbered"], + br["terminal.title.numbered"], + bs["terminal.title.numbered"], + da["terminal.title.numbered"], + de["terminal.title.numbered"], + es["terminal.title.numbered"], + fr["terminal.title.numbered"], + ja["terminal.title.numbered"], + ko["terminal.title.numbered"], + no["terminal.title.numbered"], + pl["terminal.title.numbered"], + ru["terminal.title.numbered"], + th["terminal.title.numbered"], + tr["terminal.title.numbered"], + zh["terminal.title.numbered"], + zht["terminal.title.numbered"], + ]), +) + +export function defaultTitle(number: number) { + return en["terminal.title.numbered"].replace("{{number}}", String(number)) +} + +export function isDefaultTitle(title: string, number: number) { + return numbered.some((text) => title === text.replace("{{number}}", String(number))) +} + +export function titleNumber(title: string, max: number) { + return Array.from({ length: max }, (_, idx) => idx + 1).find((number) => isDefaultTitle(title, number)) +} diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx index a2807375ff..e65c167884 100644 --- a/packages/app/src/context/terminal.tsx +++ b/packages/app/src/context/terminal.tsx @@ -4,6 +4,7 @@ import { batch, createEffect, createMemo, createRoot, on, onCleanup } from "soli import { useParams } from "@solidjs/router" import { useSDK } from "./sdk" import type { Platform } from "./platform" +import { defaultTitle, titleNumber } from "./terminal-title" import { Persist, persisted, removePersisted } from "@/utils/persist" export type LocalPTY = { @@ -33,11 +34,7 @@ function num(value: unknown) { } function numberFromTitle(title: string) { - const match = title.match(/^Terminal (\d+)$/) - if (!match) return - const value = Number(match[1]) - if (!Number.isFinite(value) || value <= 0) return - return value + return titleNumber(title, MAX_TERMINAL_SESSIONS) } function pty(value: unknown): LocalPTY | undefined { @@ -202,13 +199,13 @@ function createWorkspaceTerminalSession(sdk: ReturnType, dir: str const nextNumber = pickNextTerminalNumber() sdk.client.pty - .create({ title: `Terminal ${nextNumber}` }) + .create({ title: defaultTitle(nextNumber) }) .then((pty: { data?: { id?: string; title?: string } }) => { const id = pty.data?.id if (!id) return const newTerminal = { id, - title: pty.data?.title ?? "Terminal", + title: pty.data?.title ?? defaultTitle(nextNumber), titleNumber: nextNumber, } setStore("all", store.all.length, newTerminal) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 1b87208267..99a2d03d09 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -778,4 +778,77 @@ export const dict = { "common.time.daysAgo.short": "قبل {{count}} ي", "settings.providers.connected.environmentDescription": "متصل من متغيرات البيئة الخاصة بك", "settings.providers.custom.description": "أضف مزود متوافق مع OpenAI بواسطة عنوان URL الأساسي.", + + "app.server.unreachable": "تعذر الوصول إلى {{server}}", + "app.server.retrying": "جاري إعادة المحاولة تلقائيًا...", + "app.server.otherServers": "خوادم أخرى", + "dialog.server.add.usernamePlaceholder": "اسم المستخدم", + "dialog.server.add.passwordPlaceholder": "كلمة المرور", + "server.row.noUsername": "لا يوجد اسم مستخدم", + "session.review.noVcs.createGit.title": "إنشاء مستودع Git", + "session.review.noVcs.createGit.description": "تتبع ومراجعة والتراجع عن التغييرات في هذا المشروع", + "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", + "session.header.open.fileExplorer": "مستكشف الملفات", + "session.header.open.fileManager": "مدير الملفات", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "المحطة الطرفية", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "تشخيص أداء التطوير", + "debugBar.na": "غير متاح", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": "آخر انتقال مكتمل للمسار يمس صفحة جلسة، مُقاسًا من بدء التوجيه حتى أول رسم بعد استقراره.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "الإطارات المتجددة في الثانية خلال آخر 5 ثوانٍ.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "أسوأ وقت للإطار خلال آخر 5 ثوانٍ.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "الإطارات التي تزيد عن 32 مللي ثانية في آخر 5 ثوانٍ.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "الوقت المحظور وعدد المهام الطويلة في آخر 5 ثوانٍ. أقصى مهمة: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "أسوأ تأخير إدخال تمت ملاحظته في آخر 5 ثوانٍ.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": "مدة التفاعل التقريبية خلال آخر 5 ثوانٍ. هذا يشبه INP، وليس Web Vitals INP الرسمي.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "التحول التخطيطي التراكمي لعمر التطبيق الحالي.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "كومة JS المستخدمة مقابل حد الكومة. Chromium فقط.", + "debugBar.mem.tip": "كومة JS المستخدمة مقابل حد الكومة. {{used}} من {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Space", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "غير معروف", + "error.page.circular": "[دائري]", + "error.globalSDK.noServerAvailable": "لا يوجد خادم متاح", + "error.globalSDK.serverNotAvailable": "الخادم غير متاح", + "error.childStore.persistedCacheCreateFailed": "فشل إنشاء ذاكرة التخزين المؤقت الدائمة", + "error.childStore.persistedProjectMetadataCreateFailed": "فشل إنشاء بيانات تعريف المشروع الدائمة", + "error.childStore.persistedProjectIconCreateFailed": "فشل إنشاء أيقونة المشروع الدائمة", + "error.childStore.storeCreateFailed": "فشل إنشاء المخزن", + "terminal.connectionLost.abnormalClose": "تم إغلاق WebSocket بشكل غير طبيعي: {{code}}", } diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 9e1f7f2a0e..46ee7f114e 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -788,4 +788,79 @@ export const dict = { "common.time.daysAgo.short": "{{count}}d atrás", "settings.providers.connected.environmentDescription": "Conectado a partir de suas variáveis de ambiente", "settings.providers.custom.description": "Adicionar um provedor compatível com a OpenAI através do URL base.", + + "app.server.unreachable": "Não foi possível conectar a {{server}}", + "app.server.retrying": "Tentando novamente automaticamente...", + "app.server.otherServers": "Outros servidores", + "dialog.server.add.usernamePlaceholder": "nome de usuário", + "dialog.server.add.passwordPlaceholder": "senha", + "server.row.noUsername": "sem nome de usuário", + "session.review.noVcs.createGit.title": "Criar um repositório Git", + "session.review.noVcs.createGit.description": "Rastreie, revise e desfaça alterações neste projeto", + "session.review.noVcs.createGit.actionLoading": "Criando repositório Git...", + "session.review.noVcs.createGit.action": "Criar repositório Git", + "session.todo.progress": "{{done}} de {{total}} tarefas concluídas", + "session.question.progress": "{{current}} de {{total}} perguntas", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Explorador de Arquivos", + "session.header.open.fileManager": "Gerenciador de Arquivos", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Diagnóstico de desempenho de desenvolvimento", + "debugBar.na": "n/a", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Última transição de rota concluída tocando em uma página de sessão, medida desde o início do roteador até a primeira pintura após o estabelecimento.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Quadros por segundo nos últimos 5 segundos.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Pior tempo de quadro nos últimos 5 segundos.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Quadros acima de 32ms nos últimos 5 segundos.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Tempo bloqueado e contagem de tarefas longas nos últimos 5 segundos. Tarefa máx: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Pior atraso de entrada observado nos últimos 5 segundos.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Duração aproximada da interação nos últimos 5 segundos. Isso é semelhante ao INP, não o INP oficial do Web Vitals.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Mudança cumulativa de layout para o tempo de vida atual do aplicativo.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Heap JS usado vs limite de heap. Apenas Chromium.", + "debugBar.mem.tip": "Heap JS usado vs limite de heap. {{used}} de {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Espaço", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "desconhecido", + "error.page.circular": "[Circular]", + "error.globalSDK.noServerAvailable": "Nenhum servidor disponível", + "error.globalSDK.serverNotAvailable": "Servidor indisponível", + "error.childStore.persistedCacheCreateFailed": "Falha ao criar cache persistente", + "error.childStore.persistedProjectMetadataCreateFailed": "Falha ao criar metadados de projeto persistentes", + "error.childStore.persistedProjectIconCreateFailed": "Falha ao criar ícone de projeto persistente", + "error.childStore.storeCreateFailed": "Falha ao criar armazenamento", + "terminal.connectionLost.abnormalClose": "WebSocket fechado anormalmente: {{code}}", } diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 3151c9b221..140b838103 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -864,4 +864,79 @@ export const dict = { "common.time.daysAgo.short": "prije {{count}} d", "settings.providers.connected.environmentDescription": "Povezano sa vašim varijablama okruženja", "settings.providers.custom.description": "Dodajte provajdera kompatibilnog s OpenAI putem osnovnog URL-a.", + + "app.server.unreachable": "Nije moguće pristupiti {{server}}", + "app.server.retrying": "Automatski ponovni pokušaj...", + "app.server.otherServers": "Drugi serveri", + "dialog.server.add.usernamePlaceholder": "korisničko ime", + "dialog.server.add.passwordPlaceholder": "lozinka", + "server.row.noUsername": "nema korisničkog imena", + "session.review.noVcs.createGit.title": "Kreiraj Git repozitorij", + "session.review.noVcs.createGit.description": "Pratite, pregledajte i poništite promjene u ovom projektu", + "session.review.noVcs.createGit.actionLoading": "Kreiranje Git repozitorija...", + "session.review.noVcs.createGit.action": "Kreiraj Git repozitorij", + "session.todo.progress": "{{done}} od {{total}} zadataka završeno", + "session.question.progress": "{{current}} od {{total}} pitanja", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "File Explorer", + "session.header.open.fileManager": "File Manager", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Dijagnostika performansi razvoja", + "debugBar.na": "n/a", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Posljednji završeni prelazak rute koji dotiče stranicu sesije, mjeren od početka rutera do prvog iscrtavanja nakon smirivanja.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Kadrovi u sekundi tokom posljednjih 5 sekundi.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Najgore vrijeme kadra u posljednjih 5 sekundi.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Kadrovi duži od 32ms u posljednjih 5 sekundi.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Blokirano vrijeme i broj dugih zadataka u posljednjih 5 sekundi. Maks zadatak: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Najgore zabilježeno kašnjenje unosa u posljednjih 5 sekundi.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Približno trajanje interakcije tokom posljednjih 5 sekundi. Ovo je slično INP-u, nije službeni Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Kumulativni pomak rasporeda za trenutni životni vijek aplikacije.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Korišteni JS heap naspram limita heapa. Samo Chromium.", + "debugBar.mem.tip": "Korišteni JS heap naspram limita heapa. {{used}} od {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Space", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "nepoznato", + "error.page.circular": "[Kružno]", + "error.globalSDK.noServerAvailable": "Nema dostupnog servera", + "error.globalSDK.serverNotAvailable": "Server nije dostupan", + "error.childStore.persistedCacheCreateFailed": "Nije uspjelo kreiranje trajnog keša", + "error.childStore.persistedProjectMetadataCreateFailed": "Nije uspjelo kreiranje trajnih metapodataka projekta", + "error.childStore.persistedProjectIconCreateFailed": "Nije uspjelo kreiranje trajne ikone projekta", + "error.childStore.storeCreateFailed": "Nije uspjelo kreiranje skladišta", + "terminal.connectionLost.abnormalClose": "WebSocket zatvoren nenormalno: {{code}}", } diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 8d9331ab68..9b776c143e 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -858,4 +858,79 @@ export const dict = { "common.time.daysAgo.short": "{{count}}d siden", "settings.providers.connected.environmentDescription": "Tilsluttet fra dine miljøvariabler", "settings.providers.custom.description": "Tilføj en OpenAI-kompatibel udbyder via basis-URL.", + + "app.server.unreachable": "Kunne ikke nå {{server}}", + "app.server.retrying": "Prøver igen automatisk...", + "app.server.otherServers": "Andre servere", + "dialog.server.add.usernamePlaceholder": "brugernavn", + "dialog.server.add.passwordPlaceholder": "adgangskode", + "server.row.noUsername": "intet brugernavn", + "session.review.noVcs.createGit.title": "Opret et Git-repository", + "session.review.noVcs.createGit.description": "Spor, gennemgå og fortryd ændringer i dette projekt", + "session.review.noVcs.createGit.actionLoading": "Opretter Git-repository...", + "session.review.noVcs.createGit.action": "Opret Git-repository", + "session.todo.progress": "{{done}} af {{total}} opgaver fuldført", + "session.question.progress": "{{current}} af {{total}} spørgsmål", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Stifinder", + "session.header.open.fileManager": "Filhåndtering", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Udviklingsydelsesdiagnostik", + "debugBar.na": "n/a", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Sidste gennemførte ruteovergang, der berører en sessionsside, målt fra routerstart til den første optegning efter den falder til ro.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Rullende billeder pr. sekund over de sidste 5 sekunder.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Værste billedtid over de sidste 5 sekunder.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Billeder over 32ms i de sidste 5 sekunder.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Blokeret tid og antal lange opgaver i de sidste 5 sekunder. Maks opgave: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Værste observerede inputforsinkelse i de sidste 5 sekunder.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Omtrentlig interaktionsvarighed over de sidste 5 sekunder. Dette er INP-lignende, ikke den officielle Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Kumulativt layoutskift for den nuværende app-levetid.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Brugt JS-heap vs heap-grænse. Kun Chromium.", + "debugBar.mem.tip": "Brugt JS-heap vs heap-grænse. {{used}} af {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Mellemrum", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "ukendt", + "error.page.circular": "[Cirkulær]", + "error.globalSDK.noServerAvailable": "Ingen server tilgængelig", + "error.globalSDK.serverNotAvailable": "Server ikke tilgængelig", + "error.childStore.persistedCacheCreateFailed": "Kunne ikke oprette vedvarende cache", + "error.childStore.persistedProjectMetadataCreateFailed": "Kunne ikke oprette vedvarende projektmetadata", + "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke oprette vedvarende projektikon", + "error.childStore.storeCreateFailed": "Kunne ikke oprette lager", + "terminal.connectionLost.abnormalClose": "WebSocket lukkede unormalt: {{code}}", } diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 782b67262f..5031748b46 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -799,4 +799,80 @@ export const dict = { "common.time.daysAgo.short": "vor {{count}} Tg", "settings.providers.connected.environmentDescription": "Verbunden aus Ihren Umgebungsvariablen", "settings.providers.custom.description": "Fügen Sie einen OpenAI-kompatiblen Anbieter per Basis-URL hinzu.", + + "app.server.unreachable": "Konnte {{server}} nicht erreichen", + "app.server.retrying": "Automatische erneute Verbindung...", + "app.server.otherServers": "Andere Server", + "dialog.server.add.usernamePlaceholder": "Benutzername", + "dialog.server.add.passwordPlaceholder": "Passwort", + "server.row.noUsername": "Kein Benutzername", + "session.review.noVcs.createGit.title": "Git-Repository erstellen", + "session.review.noVcs.createGit.description": + "Änderungen in diesem Projekt verfolgen, überprüfen und rückgängig machen", + "session.review.noVcs.createGit.actionLoading": "Git-Repository wird erstellt...", + "session.review.noVcs.createGit.action": "Git-Repository erstellen", + "session.todo.progress": "{{done}} von {{total}} Aufgaben erledigt", + "session.question.progress": "{{current}} von {{total}} Fragen", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Datei-Explorer", + "session.header.open.fileManager": "Dateimanager", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Entwicklungs-Leistungsdiagnose", + "debugBar.na": "n.v.", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Letzter abgeschlossener Routenübergang, der eine Sitzungsseite berührt, gemessen vom Start des Routers bis zum ersten Rendern nach dem Einschwingen.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Gleitende Bilder pro Sekunde in den letzten 5 Sekunden.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Schlechteste Frame-Zeit in den letzten 5 Sekunden.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Frames über 32ms in den letzten 5 Sekunden.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Blockierte Zeit und Anzahl langer Aufgaben in den letzten 5 Sekunden. Max Aufgabe: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Schlechteste beobachtete Eingabeverzögerung in den letzten 5 Sekunden.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Ungefähre Interaktionsdauer in den letzten 5 Sekunden. Dies ist INP-ähnlich, nicht das offizielle Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Kumulative Layoutverschiebung für die aktuelle App-Lebensdauer.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Verwendeter JS-Heap vs Heap-Limit. Nur Chromium.", + "debugBar.mem.tip": "Verwendeter JS-Heap vs Heap-Limit. {{used}} von {{limit}}.", + "common.key.ctrl": "Strg", + "common.key.alt": "Alt", + "common.key.shift": "Umschalt", + "common.key.meta": "Meta", + "common.key.space": "Leertaste", + "common.key.backspace": "Rücktaste", + "common.key.enter": "Eingabe", + "common.key.tab": "Tab", + "common.key.delete": "Entf", + "common.key.home": "Pos1", + "common.key.end": "Ende", + "common.key.pageUp": "Bild auf", + "common.key.pageDown": "Bild ab", + "common.key.insert": "Einfg", + "common.unknown": "unbekannt", + "error.page.circular": "[Zirkulär]", + "error.globalSDK.noServerAvailable": "Kein Server verfügbar", + "error.globalSDK.serverNotAvailable": "Server nicht verfügbar", + "error.childStore.persistedCacheCreateFailed": "Dauerhafter Cache konnte nicht erstellt werden", + "error.childStore.persistedProjectMetadataCreateFailed": "Dauerhafte Projektmetadaten konnten nicht erstellt werden", + "error.childStore.persistedProjectIconCreateFailed": "Dauerhaftes Projekticon konnte nicht erstellt werden", + "error.childStore.storeCreateFailed": "Speicher konnte nicht erstellt werden", + "terminal.connectionLost.abnormalClose": "WebSocket abnormal geschlossen: {{code}}", } satisfies Partial> diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index b950cab8d6..65e878b4e9 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -306,6 +306,10 @@ export const dict = { "dialog.directory.search.placeholder": "Search folders", "dialog.directory.empty": "No folders found", + "app.server.unreachable": "Could not reach {{server}}", + "app.server.retrying": "Retrying automatically...", + "app.server.otherServers": "Other servers", + "dialog.server.title": "Servers", "dialog.server.description": "Switch which OpenCode server this app connects to.", "dialog.server.search.placeholder": "Search servers", @@ -319,7 +323,9 @@ export const dict = { "dialog.server.add.name": "Server name (optional)", "dialog.server.add.namePlaceholder": "Localhost", "dialog.server.add.username": "Username (optional)", + "dialog.server.add.usernamePlaceholder": "username", "dialog.server.add.password": "Password (optional)", + "dialog.server.add.passwordPlaceholder": "password", "dialog.server.edit.title": "Edit server", "dialog.server.default.title": "Default server", "dialog.server.default.description": @@ -335,6 +341,7 @@ export const dict = { "dialog.server.menu.delete": "Delete", "dialog.server.current": "Current Server", "dialog.server.status.default": "Default", + "server.row.noUsername": "no username", "dialog.project.edit.title": "Edit project", "dialog.project.edit.name": "Name", @@ -456,6 +463,7 @@ export const dict = { "error.page.action.checking": "Checking...", "error.page.action.checkUpdates": "Check for updates", "error.page.action.updateTo": "Update to {{version}}", + "error.page.circular": "[Circular]", "error.page.report.prefix": "Please report this error to the OpenCode team", "error.page.report.discord": "on Discord", "error.page.version": "Version: {{version}}", @@ -464,6 +472,12 @@ export const dict = { "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?", "error.globalSync.connectFailed": "Could not connect to server. Is there a server running at `{{url}}`?", + "error.globalSDK.noServerAvailable": "No server available", + "error.globalSDK.serverNotAvailable": "Server not available", + "error.childStore.persistedCacheCreateFailed": "Failed to create persisted cache", + "error.childStore.persistedProjectMetadataCreateFailed": "Failed to create persisted project metadata", + "error.childStore.persistedProjectIconCreateFailed": "Failed to create persisted project icon", + "error.childStore.storeCreateFailed": "Failed to create store", "directory.error.invalidUrl": "Invalid directory in URL.", "error.chain.unknown": "Unknown error", @@ -512,6 +526,10 @@ export const dict = { "session.review.loadingChanges": "Loading changes...", "session.review.empty": "No changes in this session yet", "session.review.noVcs": "No Git Version Control System detected, changes not displayed", + "session.review.noVcs.createGit.title": "Create a Git repository", + "session.review.noVcs.createGit.description": "Track, review, and undo changes in this project", + "session.review.noVcs.createGit.actionLoading": "Creating Git repository...", + "session.review.noVcs.createGit.action": "Create Git repository", "session.review.noSnapshot": "Snapshot tracking is disabled in config, so session changes are unavailable", "session.review.noChanges": "No changes", @@ -530,6 +548,8 @@ export const dict = { "session.todo.title": "Todos", "session.todo.collapse": "Collapse", "session.todo.expand": "Expand", + "session.todo.progress": "{{done}} of {{total}} todos completed", + "session.question.progress": "{{current}} of {{total}} questions", "session.followupDock.summary.one": "{{count}} queued message", "session.followupDock.summary.other": "{{count}} queued messages", "session.followupDock.sendNow": "Send now", @@ -555,6 +575,22 @@ export const dict = { "session.header.open.ariaLabel": "Open in {{app}}", "session.header.open.menu": "Open options", "session.header.open.copyPath": "Copy path", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "File Explorer", + "session.header.open.fileManager": "File Manager", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", "status.popover.trigger": "Status", "status.popover.ariaLabel": "Server configurations", @@ -587,6 +623,7 @@ export const dict = { "terminal.title.numbered": "Terminal {{number}}", "terminal.close": "Close terminal", "terminal.connectionLost.title": "Connection Lost", + "terminal.connectionLost.abnormalClose": "WebSocket closed abnormally: {{code}}", "terminal.connectionLost.description": "The terminal connection was interrupted. This can happen when the server restarts.", @@ -604,6 +641,21 @@ export const dict = { "common.edit": "Edit", "common.loadMore": "Load more", "common.key.esc": "ESC", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Space", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "unknown", "common.time.justNow": "Just now", "common.time.minutesAgo.short": "{{count}}m ago", @@ -623,6 +675,30 @@ export const dict = { "sidebar.project.viewAllSessions": "View all sessions", "sidebar.project.clearNotifications": "Clear notifications", + "debugBar.ariaLabel": "Development performance diagnostics", + "debugBar.na": "n/a", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Last completed route transition touching a session page, measured from router start until the first paint after it settles.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Rolling frames per second over the last 5 seconds.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Worst frame time over the last 5 seconds.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Frames over 32ms in the last 5 seconds.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Blocked time and long-task count in the last 5 seconds. Max task: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Worst observed input delay in the last 5 seconds.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Approximate interaction duration over the last 5 seconds. This is INP-like, not the official Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Cumulative layout shift for the current app lifetime.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Used JS heap vs heap limit. Chromium only.", + "debugBar.mem.tip": "Used JS heap vs heap limit. {{used}} of {{limit}}.", + "app.name.desktop": "OpenCode Desktop", "settings.section.desktop": "Desktop", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index a2633004cf..2fabd6d4c8 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -871,4 +871,79 @@ export const dict = { "common.time.daysAgo.short": "hace {{count}} d", "settings.providers.connected.environmentDescription": "Conectado desde tus variables de entorno", "settings.providers.custom.description": "Añade un proveedor compatible con OpenAI por su URL base.", + + "app.server.unreachable": "No se pudo conectar con {{server}}", + "app.server.retrying": "Reintentando automáticamente...", + "app.server.otherServers": "Otros servidores", + "dialog.server.add.usernamePlaceholder": "usuario", + "dialog.server.add.passwordPlaceholder": "contraseña", + "server.row.noUsername": "sin usuario", + "session.review.noVcs.createGit.title": "Crear repositorio Git", + "session.review.noVcs.createGit.description": "Rastrea, revisa y deshaz cambios en este proyecto", + "session.review.noVcs.createGit.actionLoading": "Creando repositorio Git...", + "session.review.noVcs.createGit.action": "Crear repositorio Git", + "session.todo.progress": "{{done}} de {{total}} tareas completadas", + "session.question.progress": "{{current}} de {{total}} preguntas", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Explorador de archivos", + "session.header.open.fileManager": "Gestor de archivos", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Diagnóstico de rendimiento de desarrollo", + "debugBar.na": "n/d", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Última transición de ruta completada tocando una página de sesión, medida desde el inicio del router hasta el primer pintado después de asentarse.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Cuadros por segundo en los últimos 5 segundos.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Peor tiempo de cuadro en los últimos 5 segundos.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Cuadros superiores a 32ms en los últimos 5 segundos.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Tiempo bloqueado y recuento de tareas largas en los últimos 5 segundos. Tarea máx: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Peor retraso de entrada observado en los últimos 5 segundos.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Duración aproximada de la interacción en los últimos 5 segundos. Esto es similar a INP, no el INP oficial de Web Vitals.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Cambio de diseño acumulativo para la vida útil actual de la aplicación.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Heap JS usado vs límite de heap. Solo Chromium.", + "debugBar.mem.tip": "Heap JS usado vs límite de heap. {{used}} de {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Mayús", + "common.key.meta": "Meta", + "common.key.space": "Espacio", + "common.key.backspace": "Retroceso", + "common.key.enter": "Intro", + "common.key.tab": "Tab", + "common.key.delete": "Supr", + "common.key.home": "Inicio", + "common.key.end": "Fin", + "common.key.pageUp": "RePág", + "common.key.pageDown": "AvPág", + "common.key.insert": "Insert", + "common.unknown": "desconocido", + "error.page.circular": "[Circular]", + "error.globalSDK.noServerAvailable": "Ningún servidor disponible", + "error.globalSDK.serverNotAvailable": "Servidor no disponible", + "error.childStore.persistedCacheCreateFailed": "Error al crear caché persistente", + "error.childStore.persistedProjectMetadataCreateFailed": "Error al crear metadatos de proyecto persistentes", + "error.childStore.persistedProjectIconCreateFailed": "Error al crear icono de proyecto persistente", + "error.childStore.storeCreateFailed": "Error al crear almacén", + "terminal.connectionLost.abnormalClose": "WebSocket cerrado anormalmente: {{code}}", } diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index cbde19316d..dc30a0e537 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -796,4 +796,81 @@ export const dict = { "common.time.daysAgo.short": "il y a {{count}}j", "settings.providers.connected.environmentDescription": "Connecté à partir de vos variables d'environnement", "settings.providers.custom.description": "Ajouter un fournisseur compatible avec OpenAI via l'URL de base.", + + "app.server.unreachable": "Impossible de joindre {{server}}", + "app.server.retrying": "Nouvelle tentative automatique...", + "app.server.otherServers": "Autres serveurs", + "dialog.server.add.usernamePlaceholder": "nom d'utilisateur", + "dialog.server.add.passwordPlaceholder": "mot de passe", + "server.row.noUsername": "aucun nom d'utilisateur", + "session.review.noVcs.createGit.title": "Créer un dépôt Git", + "session.review.noVcs.createGit.description": "Suivre, examiner et annuler les modifications dans ce projet", + "session.review.noVcs.createGit.actionLoading": "Création du dépôt Git...", + "session.review.noVcs.createGit.action": "Créer un dépôt Git", + "session.todo.progress": "{{done}} tâches sur {{total}} terminées", + "session.question.progress": "{{current}} questions sur {{total}}", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Explorateur de fichiers", + "session.header.open.fileManager": "Gestionnaire de fichiers", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Diagnostics de performance de développement", + "debugBar.na": "n/a", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Dernière transition de route terminée touchant une page de session, mesurée du début du routeur jusqu'au premier affichage après stabilisation.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Images par seconde glissantes sur les 5 dernières secondes.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Pire temps d'image sur les 5 dernières secondes.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Images de plus de 32ms au cours des 5 dernières secondes.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": + "Temps bloqué et nombre de tâches longues au cours des 5 dernières secondes. Tâche max : {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Pire délai d'entrée observé au cours des 5 dernières secondes.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Durée approximative d'interaction au cours des 5 dernières secondes. Ceci est similaire à INP, pas le INP officiel des Web Vitals.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Décalage cumulatif de la mise en page pour la durée de vie actuelle de l'application.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Tas JS utilisé vs limite de tas. Chromium uniquement.", + "debugBar.mem.tip": "Tas JS utilisé vs limite de tas. {{used}} sur {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Maj", + "common.key.meta": "Méta", + "common.key.space": "Espace", + "common.key.backspace": "Retour arrière", + "common.key.enter": "Entrée", + "common.key.tab": "Tab", + "common.key.delete": "Suppr", + "common.key.home": "Début", + "common.key.end": "Fin", + "common.key.pageUp": "Page précédente", + "common.key.pageDown": "Page suivante", + "common.key.insert": "Inser", + "common.unknown": "inconnu", + "error.page.circular": "[Circulaire]", + "error.globalSDK.noServerAvailable": "Aucun serveur disponible", + "error.globalSDK.serverNotAvailable": "Serveur non disponible", + "error.childStore.persistedCacheCreateFailed": "Échec de la création du cache persistant", + "error.childStore.persistedProjectMetadataCreateFailed": + "Échec de la création des métadonnées de projet persistantes", + "error.childStore.persistedProjectIconCreateFailed": "Échec de la création de l'icône de projet persistante", + "error.childStore.storeCreateFailed": "Échec de la création du stockage", + "terminal.connectionLost.abnormalClose": "WebSocket fermé anormalement : {{code}}", } diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 914ac5cd79..1f5615c9bd 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -783,4 +783,78 @@ export const dict = { "common.time.daysAgo.short": "{{count}} 日前", "settings.providers.connected.environmentDescription": "環境変数から接続されました", "settings.providers.custom.description": "ベース URL を指定して OpenAI 互換のプロバイダーを追加します。", + + "app.server.unreachable": "{{server}} に到達できませんでした", + "app.server.retrying": "自動的に再試行中...", + "app.server.otherServers": "その他のサーバー", + "dialog.server.add.usernamePlaceholder": "ユーザー名", + "dialog.server.add.passwordPlaceholder": "パスワード", + "server.row.noUsername": "ユーザー名なし", + "session.review.noVcs.createGit.title": "Git リポジトリを作成", + "session.review.noVcs.createGit.description": "このプロジェクトの変更を追跡、レビュー、元に戻す", + "session.review.noVcs.createGit.actionLoading": "Git リポジトリを作成中...", + "session.review.noVcs.createGit.action": "Git リポジトリを作成", + "session.todo.progress": "{{done}} 個中 {{total}} 個の Todo が完了", + "session.question.progress": "{{total}} 問中 {{current}} 問", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "エクスプローラー", + "session.header.open.fileManager": "ファイルマネージャー", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "ターミナル", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "開発パフォーマンス診断", + "debugBar.na": "n/a", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": "セッションページに触れる最後に完了したルート遷移。ルーター開始から安定後の最初の描画まで測定。", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "過去5秒間のローリングフレーム/秒。", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "過去5秒間の最悪フレーム時間。", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "過去5秒間で32msを超えたフレーム。", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "過去5秒間のブロック時間と長時間タスク数。最大タスク: {{max}}。", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "過去5秒間で観測された最悪の入力遅延。", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "過去5秒間の概算インタラクション時間。これは INP に似ていますが、公式の Web Vitals INP ではありません。", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "現在のアプリ寿命の累積レイアウトシフト。", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "使用中の JS ヒープ対ヒープ制限。Chromium のみ。", + "debugBar.mem.tip": "使用中の JS ヒープ対ヒープ制限。{{limit}} 中 {{used}}。", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Space", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "不明", + "error.page.circular": "[循環]", + "error.globalSDK.noServerAvailable": "利用可能なサーバーがありません", + "error.globalSDK.serverNotAvailable": "サーバーが利用できません", + "error.childStore.persistedCacheCreateFailed": "永続キャッシュの作成に失敗しました", + "error.childStore.persistedProjectMetadataCreateFailed": "永続プロジェクトメタデータの作成に失敗しました", + "error.childStore.persistedProjectIconCreateFailed": "永続プロジェクトアイコンの作成に失敗しました", + "error.childStore.storeCreateFailed": "ストアの作成に失敗しました", + "terminal.connectionLost.abnormalClose": "WebSocket が異常終了しました: {{code}}", } diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index f0a3f3ae6b..a2f5e5c7c8 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -782,4 +782,78 @@ export const dict = { "common.time.daysAgo.short": "{{count}}일 전", "settings.providers.connected.environmentDescription": "환경 변수에서 연결됨", "settings.providers.custom.description": "기본 URL로 OpenAI 호환 공급자를 추가합니다.", + + "app.server.unreachable": "{{server}}에 연결할 수 없습니다", + "app.server.retrying": "자동으로 재시도 중...", + "app.server.otherServers": "다른 서버", + "dialog.server.add.usernamePlaceholder": "사용자 이름", + "dialog.server.add.passwordPlaceholder": "비밀번호", + "server.row.noUsername": "사용자 이름 없음", + "session.review.noVcs.createGit.title": "Git 저장소 생성", + "session.review.noVcs.createGit.description": "이 프로젝트의 변경 사항을 추적, 검토 및 실행 취소", + "session.review.noVcs.createGit.actionLoading": "Git 저장소 생성 중...", + "session.review.noVcs.createGit.action": "Git 저장소 생성", + "session.todo.progress": "{{total}}개의 할 일 중 {{done}}개 완료", + "session.question.progress": "{{total}}개의 질문 중 {{current}}개", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "파일 탐색기", + "session.header.open.fileManager": "파일 관리자", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "터미널", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "개발 성능 진단", + "debugBar.na": "해당 없음", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "세션 페이지에 닿은 마지막 완료된 라우트 전환. 라우터 시작부터 정착 후 첫 번째 페인트까지 측정됨.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "지난 5초간의 초당 프레임 수.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "지난 5초간의 최악의 프레임 시간.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "지난 5초간 32ms를 초과한 프레임.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "지난 5초간의 차단된 시간 및 긴 작업 수. 최대 작업: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "지난 5초간 관찰된 최악의 입력 지연.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": "지난 5초간의 대략적인 상호작용 지속 시간. 이것은 공식 Web Vitals INP가 아닌 INP와 유사합니다.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "현재 앱 수명 동안의 누적 레이아웃 이동.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "사용된 JS 힙 대 힙 제한. Chromium 전용.", + "debugBar.mem.tip": "사용된 JS 힙 대 힙 제한. {{limit}} 중 {{used}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Space", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "알 수 없음", + "error.page.circular": "[순환]", + "error.globalSDK.noServerAvailable": "사용 가능한 서버 없음", + "error.globalSDK.serverNotAvailable": "서버를 사용할 수 없음", + "error.childStore.persistedCacheCreateFailed": "영구 캐시 생성 실패", + "error.childStore.persistedProjectMetadataCreateFailed": "영구 프로젝트 메타데이터 생성 실패", + "error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패", + "error.childStore.storeCreateFailed": "저장소 생성 실패", + "terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}", } diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 17ba96058b..ed75e556ea 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -865,4 +865,79 @@ export const dict = { "common.time.daysAgo.short": "{{count}} d siden", "settings.providers.connected.environmentDescription": "Koblet til fra miljøvariablene dine", "settings.providers.custom.description": "Legg til en OpenAI-kompatibel leverandør via basis-URL.", + + "app.server.unreachable": "Kunne ikke nå {{server}}", + "app.server.retrying": "Prøver på nytt automatisk...", + "app.server.otherServers": "Andre servere", + "dialog.server.add.usernamePlaceholder": "brukernavn", + "dialog.server.add.passwordPlaceholder": "passord", + "server.row.noUsername": "inget brukernavn", + "session.review.noVcs.createGit.title": "Opprett et Git-depot", + "session.review.noVcs.createGit.description": "Spor, gjennomgå og angre endringer i dette prosjektet", + "session.review.noVcs.createGit.actionLoading": "Oppretter Git-depot...", + "session.review.noVcs.createGit.action": "Opprett Git-depot", + "session.todo.progress": "{{done}} av {{total}} oppgaver fullført", + "session.question.progress": "{{current}} av {{total}} spørsmål", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Filutforsker", + "session.header.open.fileManager": "Filbehandler", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Utviklingsytelsesdiagnostikk", + "debugBar.na": "i/t", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Siste fullførte ruteovergang som berører en sesjonsside, målt fra ruterstart til første opptegning etter at den har roet seg.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Rullende bilder per sekund over de siste 5 sekundene.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Verste bildetid over de siste 5 sekundene.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Bilder over 32ms i de siste 5 sekundene.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Blokkert tid og antall lange oppgaver i de siste 5 sekundene. Maks oppgave: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Verste observerte inndataforsinkelse i de siste 5 sekundene.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Omtrentlig interaksjonsvarighet over de siste 5 sekundene. Dette er INP-lignende, ikke den offisielle Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Kumulativ layoutforskyvning for gjeldende app-levetid.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Brukt JS-heap vs heap-grense. Kun Chromium.", + "debugBar.mem.tip": "Brukt JS-heap vs heap-grense. {{used}} av {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Mellomrom", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "ukjent", + "error.page.circular": "[Sirkulær]", + "error.globalSDK.noServerAvailable": "Ingen server tilgjengelig", + "error.globalSDK.serverNotAvailable": "Server ikke tilgjengelig", + "error.childStore.persistedCacheCreateFailed": "Kunne ikke opprette vedvarende hurtigbuffer", + "error.childStore.persistedProjectMetadataCreateFailed": "Kunne ikke opprette vedvarende prosjektmetadata", + "error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon", + "error.childStore.storeCreateFailed": "Kunne ikke opprette lager", + "terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}", } satisfies Partial> diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 38111c8738..2507acd9d2 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -785,4 +785,80 @@ export const dict = { "common.time.daysAgo.short": "{{count}} dni temu", "settings.providers.connected.environmentDescription": "Połączono ze zmiennymi środowiskowymi", "settings.providers.custom.description": "Dodaj dostawcę zgodnego z OpenAI poprzez podstawowy URL.", + + "app.server.unreachable": "Nie można połączyć z {{server}}", + "app.server.retrying": "Ponawianie automatycznie...", + "app.server.otherServers": "Inne serwery", + "dialog.server.add.usernamePlaceholder": "nazwa użytkownika", + "dialog.server.add.passwordPlaceholder": "hasło", + "server.row.noUsername": "brak nazwy użytkownika", + "session.review.noVcs.createGit.title": "Utwórz repozytorium Git", + "session.review.noVcs.createGit.description": "Śledź, przeglądaj i cofaj zmiany w tym projekcie", + "session.review.noVcs.createGit.actionLoading": "Tworzenie repozytorium Git...", + "session.review.noVcs.createGit.action": "Utwórz repozytorium Git", + "session.todo.progress": "Ukończono {{done}} z {{total}} zadań", + "session.question.progress": "{{current}} z {{total}} pytań", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Eksplorator plików", + "session.header.open.fileManager": "Menedżer plików", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Diagnostyka wydajności deweloperskiej", + "debugBar.na": "n.d.", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Ostatnie zakończone przejście trasy dotykające strony sesji, mierzone od startu routera do pierwszego odrysowania po ustaleniu.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Średnia liczba klatek na sekundę w ciągu ostatnich 5 sekund.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Najgorszy czas klatki w ciągu ostatnich 5 sekund.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Klatki powyżej 32ms w ciągu ostatnich 5 sekund.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": + "Zablokowany czas i liczba długich zadań w ciągu ostatnich 5 sekund. Maksymalne zadanie: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Najgorsze zaobserwowane opóźnienie wejścia w ciągu ostatnich 5 sekund.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Przybliżony czas trwania interakcji w ciągu ostatnich 5 sekund. Jest to podobne do INP, a nie oficjalne Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Skumulowane przesunięcie układu dla bieżącego czasu życia aplikacji.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Użyta sterta JS vs limit sterty. Tylko Chromium.", + "debugBar.mem.tip": "Użyta sterta JS vs limit sterty. {{used}} z {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Spacja", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "nieznany", + "error.page.circular": "[Cykliczne]", + "error.globalSDK.noServerAvailable": "Brak dostępnego serwera", + "error.globalSDK.serverNotAvailable": "Serwer niedostępny", + "error.childStore.persistedCacheCreateFailed": "Nie udało się utworzyć trwałej pamięci podręcznej", + "error.childStore.persistedProjectMetadataCreateFailed": "Nie udało się utworzyć trwałych metadanych projektu", + "error.childStore.persistedProjectIconCreateFailed": "Nie udało się utworzyć trwałej ikony projektu", + "error.childStore.storeCreateFailed": "Nie udało się utworzyć magazynu", + "terminal.connectionLost.abnormalClose": "WebSocket zamknięty nieprawidłowo: {{code}}", } diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 0b63e84227..6145b3011b 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -867,4 +867,79 @@ export const dict = { "common.time.daysAgo.short": "{{count}} д назад", "settings.providers.connected.environmentDescription": "Подключено из ваших переменных окружения", "settings.providers.custom.description": "Добавить провайдера, совместимого с OpenAI, по базовому URL.", + + "app.server.unreachable": "Не удалось связаться с {{server}}", + "app.server.retrying": "Автоматическая повторная попытка...", + "app.server.otherServers": "Другие серверы", + "dialog.server.add.usernamePlaceholder": "имя пользователя", + "dialog.server.add.passwordPlaceholder": "пароль", + "server.row.noUsername": "нет имени пользователя", + "session.review.noVcs.createGit.title": "Создать репозиторий Git", + "session.review.noVcs.createGit.description": "Отслеживайте, просматривайте и отменяйте изменения в этом проекте", + "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", + "session.header.open.fileExplorer": "Проводник", + "session.header.open.fileManager": "Файловый менеджер", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Терминал", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Диагностика производительности разработки", + "debugBar.na": "н/д", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Последний завершенный переход маршрута, затрагивающий страницу сеанса, измеренный от запуска маршрутизатора до первой отрисовки после стабилизации.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Скользящая частота кадров в секунду за последние 5 секунд.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Худшее время кадра за последние 5 секунд.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Кадры более 32 мс за последние 5 секунд.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Заблокированное время и количество длинных задач за последние 5 секунд. Макс. задача: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Худшая наблюдаемая задержка ввода за последние 5 секунд.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "Приблизительная продолжительность взаимодействия за последние 5 секунд. Это похоже на INP, а не официальный Web Vitals INP.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Кумулятивный сдвиг макета за текущее время жизни приложения.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Используемая куча JS по сравнению с лимитом кучи. Только Chromium.", + "debugBar.mem.tip": "Используемая куча JS по сравнению с лимитом кучи. {{used}} из {{limit}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Пробел", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "неизвестно", + "error.page.circular": "[Циклично]", + "error.globalSDK.noServerAvailable": "Нет доступного сервера", + "error.globalSDK.serverNotAvailable": "Сервер недоступен", + "error.childStore.persistedCacheCreateFailed": "Не удалось создать постоянный кэш", + "error.childStore.persistedProjectMetadataCreateFailed": "Не удалось создать постоянные метаданные проекта", + "error.childStore.persistedProjectIconCreateFailed": "Не удалось создать постоянный значок проекта", + "error.childStore.storeCreateFailed": "Не удалось создать хранилище", + "terminal.connectionLost.abnormalClose": "WebSocket закрыт аварийно: {{code}}", } diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 94bb2c2c31..9cc3c5be1d 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -854,4 +854,79 @@ export const dict = { "common.time.daysAgo.short": "{{count}} วันที่แล้ว", "settings.providers.connected.environmentDescription": "เชื่อมต่อจากตัวแปรสภาพแวดล้อมของคุณ", "settings.providers.custom.description": "เพิ่มผู้ให้บริการที่รองรับ OpenAI ด้วย URL หลัก", + + "app.server.unreachable": "ไม่สามารถติดต่อ {{server}}", + "app.server.retrying": "กำลังลองใหม่โดยอัตโนมัติ...", + "app.server.otherServers": "เซิร์ฟเวอร์อื่น ๆ", + "dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้", + "dialog.server.add.passwordPlaceholder": "รหัสผ่าน", + "server.row.noUsername": "ไม่มีชื่อผู้ใช้", + "session.review.noVcs.createGit.title": "สร้าง Git repository", + "session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้", + "session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git repository...", + "session.review.noVcs.createGit.action": "สร้าง Git repository", + "session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ", + "session.question.progress": "{{current}} จาก {{total}} คำถาม", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "File Explorer", + "session.header.open.fileManager": "File Manager", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "การวินิจฉัยประสิทธิภาพการพัฒนา", + "debugBar.na": "n/a", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "การเปลี่ยนเส้นทางที่เสร็จสมบูรณ์ล่าสุดที่สัมผัสหน้าเซสชัน วัดจากจุดเริ่มต้นเราเตอร์จนถึงการวาดครั้งแรกหลังจากที่นิ่ง", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "เฟรมต่อวินาทีแบบต่อเนื่องในช่วง 5 วินาทีที่ผ่านมา", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "เวลาเฟรมที่แย่ที่สุดในช่วง 5 วินาทีที่ผ่านมา", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "เฟรมที่เกิน 32ms ในช่วง 5 วินาทีที่ผ่านมา", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "เวลาที่ถูกบล็อกและจำนวนงานยาวในช่วง 5 วินาทีที่ผ่านมา งานสูงสุด: {{max}}", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "ความล่าช้าในการป้อนข้อมูลที่แย่ที่สุดที่สังเกตได้ในช่วง 5 วินาทีที่ผ่านมา", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": + "ระยะเวลาการโต้ตอบโดยประมาณในช่วง 5 วินาทีที่ผ่านมา นี่เป็นเหมือน INP ไม่ใช่ Web Vitals INP อย่างเป็นทางการ", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "การเลื่อนเลย์เอาต์สะสมสำหรับอายุการใช้งานของแอปปัจจุบัน", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "JS heap ที่ใช้เทียบกับขีดจำกัด heap เฉพาะ Chromium", + "debugBar.mem.tip": "JS heap ที่ใช้เทียบกับขีดจำกัด heap {{used}} จาก {{limit}}", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Space", + "common.key.backspace": "Backspace", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "ไม่ทราบ", + "error.page.circular": "[วงกลม]", + "error.globalSDK.noServerAvailable": "ไม่มีเซิร์ฟเวอร์", + "error.globalSDK.serverNotAvailable": "เซิร์ฟเวอร์ไม่พร้อมใช้งาน", + "error.childStore.persistedCacheCreateFailed": "ไม่สามารถสร้างแคชถาวร", + "error.childStore.persistedProjectMetadataCreateFailed": "ไม่สามารถสร้างเมตาดาต้าโปรเจกต์ถาวร", + "error.childStore.persistedProjectIconCreateFailed": "ไม่สามารถสร้างไอคอนโปรเจกต์ถาวร", + "error.childStore.storeCreateFailed": "ไม่สามารถสร้างที่เก็บ", + "terminal.connectionLost.abnormalClose": "WebSocket ปิดอย่างผิดปกติ: {{code}}", } diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index 66835c1c57..373f26ad6f 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -874,4 +874,78 @@ export const dict = { "common.time.daysAgo.short": "{{count}}g önce", "settings.providers.connected.environmentDescription": "Ortam değişkenlerinizden bağlandı", "settings.providers.custom.description": "Temel URL üzerinden OpenAI uyumlu bir sağlayıcı ekleyin.", + + "app.server.unreachable": "{{server}} sunucusuna ulaşılamadı", + "app.server.retrying": "Otomatik olarak tekrar deneniyor...", + "app.server.otherServers": "Diğer sunucular", + "dialog.server.add.usernamePlaceholder": "kullanıcı adı", + "dialog.server.add.passwordPlaceholder": "parola", + "server.row.noUsername": "kullanıcı adı yok", + "session.review.noVcs.createGit.title": "Git deposu oluştur", + "session.review.noVcs.createGit.description": "Bu projedeki değişiklikleri takip et, incele ve geri al", + "session.review.noVcs.createGit.actionLoading": "Git deposu oluşturuluyor...", + "session.review.noVcs.createGit.action": "Git deposu oluştur", + "session.todo.progress": "{{total}} görevin {{done}} tanesi tamamlandı", + "session.question.progress": "{{total}} sorunun {{current}} tanesi", + "session.header.open.finder": "Finder", + "session.header.open.fileExplorer": "Dosya Gezgini", + "session.header.open.fileManager": "Dosya Yöneticisi", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "Terminal", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "Geliştirme performansı teşhisi", + "debugBar.na": "yok", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": + "Yönlendirici başlangıcından yerleşme sonrası ilk boyamaya kadar ölçülen, bir oturum sayfasına dokunan son tamamlanmış rota geçişi.", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "Son 5 saniyedeki kayan saniye başına kare sayısı.", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "Son 5 saniyedeki en kötü kare süresi.", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "Son 5 saniyede 32ms üzerindeki kareler.", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "Son 5 saniyedeki engellenen süre ve uzun görev sayısı. Maksimum görev: {{max}}.", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "Son 5 saniyede gözlemlenen en kötü giriş gecikmesi.", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": "Son 5 saniyedeki yaklaşık etkileşim süresi. Bu INP benzeridir, resmi Web Vitals INP değildir.", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "Mevcut uygulama ömrü için kümülatif düzen kayması.", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "Kullanılan JS yığını vs yığın sınırı. Yalnızca Chromium.", + "debugBar.mem.tip": "Kullanılan JS yığını vs yığın sınırı. {{limit}} içinde {{used}}.", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "Boşluk", + "common.key.backspace": "Geri", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "bilinmiyor", + "error.page.circular": "[Döngüsel]", + "error.globalSDK.noServerAvailable": "Sunucu yok", + "error.globalSDK.serverNotAvailable": "Sunucu mevcut değil", + "error.childStore.persistedCacheCreateFailed": "Kalıcı önbellek oluşturulamadı", + "error.childStore.persistedProjectMetadataCreateFailed": "Kalıcı proje meta verileri oluşturulamadı", + "error.childStore.persistedProjectIconCreateFailed": "Kalıcı proje simgesi oluşturulamadı", + "error.childStore.storeCreateFailed": "Depo oluşturulamadı", + "terminal.connectionLost.abnormalClose": "WebSocket anormal şekilde kapandı: {{code}}", } satisfies Partial> diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index d6b179c5c5..819e1cd87d 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -853,4 +853,77 @@ export const dict = { "common.time.daysAgo.short": "{{count}}天前", "settings.providers.connected.environmentDescription": "已通过环境变量连接", "settings.providers.custom.description": "通过基础 URL 添加与 OpenAI 兼容的提供商。", + + "app.server.unreachable": "无法连接到 {{server}}", + "app.server.retrying": "正在自动重试...", + "app.server.otherServers": "其他服务器", + "dialog.server.add.usernamePlaceholder": "用户名", + "dialog.server.add.passwordPlaceholder": "密码", + "server.row.noUsername": "无用户名", + "session.review.noVcs.createGit.title": "创建 Git 仓库", + "session.review.noVcs.createGit.description": "在此项目中跟踪、审查和撤消更改", + "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": "访达", + "session.header.open.fileExplorer": "文件资源管理器", + "session.header.open.fileManager": "文件管理器", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "终端", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "开发性能诊断", + "debugBar.na": "不适用", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": "最后一次完成的涉及会话页面的路由转换,从路由器启动到稳定后的第一次绘制。", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "过去 5 秒内的滚动帧率。", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "过去 5 秒内最差的帧时间。", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "过去 5 秒内超过 32ms 的帧。", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "过去 5 秒内的阻塞时间和长任务计数。最大任务:{{max}}。", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "过去 5 秒内观察到的最差输入延迟。", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": "过去 5 秒内的近似交互持续时间。这类似于 INP,而非官方的 Web Vitals INP。", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "当前应用生命周期的累积布局偏移。", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "使用的 JS 堆与堆限制。仅限 Chromium。", + "debugBar.mem.tip": "使用的 JS 堆与堆限制。{{used}} / {{limit}}。", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "空格", + "common.key.backspace": "退格", + "common.key.enter": "回车", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "未知", + "error.page.circular": "[循环]", + "error.globalSDK.noServerAvailable": "无可用服务器", + "error.globalSDK.serverNotAvailable": "服务器不可用", + "error.childStore.persistedCacheCreateFailed": "创建持久化缓存失败", + "error.childStore.persistedProjectMetadataCreateFailed": "创建持久化项目元数据失败", + "error.childStore.persistedProjectIconCreateFailed": "创建持久化项目图标失败", + "error.childStore.storeCreateFailed": "创建存储失败", + "terminal.connectionLost.abnormalClose": "WebSocket 异常关闭:{{code}}", } satisfies Partial> diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 3796350d2c..8c80cd3235 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -848,4 +848,77 @@ export const dict = { "common.time.daysAgo.short": "{{count}}天前", "settings.providers.connected.environmentDescription": "已從環境變數連線", "settings.providers.custom.description": "透過基本 URL 新增與 OpenAI 相容的提供者。", + + "app.server.unreachable": "無法連線至 {{server}}", + "app.server.retrying": "正在自動重試...", + "app.server.otherServers": "其他伺服器", + "dialog.server.add.usernamePlaceholder": "使用者名稱", + "dialog.server.add.passwordPlaceholder": "密碼", + "server.row.noUsername": "無使用者名稱", + "session.review.noVcs.createGit.title": "建立 Git 儲存庫", + "session.review.noVcs.createGit.description": "追蹤、檢閱及復原此專案中的變更", + "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", + "session.header.open.fileExplorer": "檔案總管", + "session.header.open.fileManager": "檔案管理員", + "session.header.open.app.vscode": "VS Code", + "session.header.open.app.cursor": "Cursor", + "session.header.open.app.zed": "Zed", + "session.header.open.app.textmate": "TextMate", + "session.header.open.app.antigravity": "Antigravity", + "session.header.open.app.terminal": "終端機", + "session.header.open.app.iterm2": "iTerm2", + "session.header.open.app.ghostty": "Ghostty", + "session.header.open.app.warp": "Warp", + "session.header.open.app.xcode": "Xcode", + "session.header.open.app.androidStudio": "Android Studio", + "session.header.open.app.powershell": "PowerShell", + "session.header.open.app.sublimeText": "Sublime Text", + "debugBar.ariaLabel": "開發效能診斷", + "debugBar.na": "不適用", + "debugBar.nav.label": "NAV", + "debugBar.nav.tip": "最後一次完成的涉及工作階段頁面的路由轉換,從路由器啟動到穩定後的第一次繪製。", + "debugBar.fps.label": "FPS", + "debugBar.fps.tip": "過去 5 秒內的滾動幀率。", + "debugBar.frame.label": "FRAME", + "debugBar.frame.tip": "過去 5 秒內最差的幀時間。", + "debugBar.jank.label": "JANK", + "debugBar.jank.tip": "過去 5 秒內超過 32ms 的幀。", + "debugBar.long.label": "LONG", + "debugBar.long.tip": "過去 5 秒內的阻塞時間和長任務計數。最大任務:{{max}}。", + "debugBar.delay.label": "DELAY", + "debugBar.delay.tip": "過去 5 秒內觀察到的最差輸入延遲。", + "debugBar.inp.label": "INP", + "debugBar.inp.tip": "過去 5 秒內的近似互動持續時間。這類似於 INP,而非官方的 Web Vitals INP。", + "debugBar.cls.label": "CLS", + "debugBar.cls.tip": "目前應用程式生命週期的累積版面配置位移。", + "debugBar.mem.label": "MEM", + "debugBar.mem.tipUnavailable": "使用的 JS 堆積與堆積限制。僅限 Chromium。", + "debugBar.mem.tip": "使用的 JS 堆積與堆積限制。{{used}} / {{limit}}。", + "common.key.ctrl": "Ctrl", + "common.key.alt": "Alt", + "common.key.shift": "Shift", + "common.key.meta": "Meta", + "common.key.space": "空白鍵", + "common.key.backspace": "退格鍵", + "common.key.enter": "Enter", + "common.key.tab": "Tab", + "common.key.delete": "Delete", + "common.key.home": "Home", + "common.key.end": "End", + "common.key.pageUp": "Page Up", + "common.key.pageDown": "Page Down", + "common.key.insert": "Insert", + "common.unknown": "未知", + "error.page.circular": "[循環]", + "error.globalSDK.noServerAvailable": "無可用的伺服器", + "error.globalSDK.serverNotAvailable": "伺服器無法使用", + "error.childStore.persistedCacheCreateFailed": "建立持續性快取失敗", + "error.childStore.persistedProjectMetadataCreateFailed": "建立持續性專案中繼資料失敗", + "error.childStore.persistedProjectIconCreateFailed": "建立持續性專案圖示失敗", + "error.childStore.storeCreateFailed": "建立儲存區失敗", + "terminal.connectionLost.abnormalClose": "WebSocket 異常關閉:{{code}}", } satisfies Partial> diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx index a30d86d180..11284b3d2d 100644 --- a/packages/app/src/pages/error.tsx +++ b/packages/app/src/pages/error.tsx @@ -35,14 +35,14 @@ function isInitError(error: unknown): error is InitError { ) } -function safeJson(value: unknown): string { +function safeJson(value: unknown, circular: string): string { const seen = new WeakSet() const json = JSON.stringify( value, (_key, val) => { if (typeof val === "bigint") return val.toString() if (typeof val === "object" && val) { - if (seen.has(val)) return "[Circular]" + if (seen.has(val)) return circular seen.add(val) } return val @@ -54,14 +54,15 @@ function safeJson(value: unknown): string { function formatInitError(error: InitError, t: Translator): string { const data = error.data + const json = (value: unknown) => safeJson(value, t("error.page.circular")) switch (error.name) { case "MCPFailed": { const name = typeof data.name === "string" ? data.name : "" return t("error.chain.mcpFailed", { name }) } case "ProviderAuthError": { - const providerID = typeof data.providerID === "string" ? data.providerID : "unknown" - const message = typeof data.message === "string" ? data.message : safeJson(data.message) + const providerID = typeof data.providerID === "string" ? data.providerID : t("common.unknown") + const message = typeof data.message === "string" ? data.message : json(data.message) return t("error.chain.providerAuthFailed", { provider: providerID, message }) } case "APIError": { @@ -101,24 +102,24 @@ function formatInitError(error: InitError, t: Translator): string { ].join("\n") } case "ProviderInitError": { - const providerID = typeof data.providerID === "string" ? data.providerID : "unknown" + const providerID = typeof data.providerID === "string" ? data.providerID : t("common.unknown") return t("error.chain.providerInitFailed", { provider: providerID }) } case "ConfigJsonError": { - const path = typeof data.path === "string" ? data.path : safeJson(data.path) + const path = typeof data.path === "string" ? data.path : json(data.path) const message = typeof data.message === "string" ? data.message : "" if (message) return t("error.chain.configJsonInvalidWithMessage", { path, message }) return t("error.chain.configJsonInvalid", { path }) } case "ConfigDirectoryTypoError": { - const path = typeof data.path === "string" ? data.path : safeJson(data.path) - const dir = typeof data.dir === "string" ? data.dir : safeJson(data.dir) - const suggestion = typeof data.suggestion === "string" ? data.suggestion : safeJson(data.suggestion) + const path = typeof data.path === "string" ? data.path : json(data.path) + const dir = typeof data.dir === "string" ? data.dir : json(data.dir) + const suggestion = typeof data.suggestion === "string" ? data.suggestion : json(data.suggestion) return t("error.chain.configDirectoryTypo", { dir, path, suggestion }) } case "ConfigFrontmatterError": { - const path = typeof data.path === "string" ? data.path : safeJson(data.path) - const message = typeof data.message === "string" ? data.message : safeJson(data.message) + const path = typeof data.path === "string" ? data.path : json(data.path) + const message = typeof data.message === "string" ? data.message : json(data.message) return t("error.chain.configFrontmatterError", { path, message }) } case "ConfigInvalidError": { @@ -126,7 +127,7 @@ function formatInitError(error: InitError, t: Translator): string { ? data.issues.filter(isIssue).map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) : [] const message = typeof data.message === "string" ? data.message : "" - const path = typeof data.path === "string" ? data.path : safeJson(data.path) + const path = typeof data.path === "string" ? data.path : json(data.path) const line = message ? t("error.chain.configInvalidWithMessage", { path, message }) @@ -135,14 +136,15 @@ function formatInitError(error: InitError, t: Translator): string { return [line, ...issues].join("\n") } case "UnknownError": - return typeof data.message === "string" ? data.message : safeJson(data) + return typeof data.message === "string" ? data.message : json(data) default: if (typeof data.message === "string") return data.message - return safeJson(data) + return json(data) } } function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessage?: string): string { + const json = (value: unknown) => safeJson(value, t("error.page.circular")) if (!error) return t("error.chain.unknown") if (isInitError(error)) { @@ -204,7 +206,7 @@ function formatErrorChain(error: unknown, t: Translator, depth = 0, parentMessag } const indent = depth > 0 ? `\n${CHAIN_SEPARATOR}${t("error.chain.causedBy")}\n` : "" - return indent + safeJson(error) + return indent + json(error) } function formatError(error: unknown, t: Translator): string { diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index c1088622a2..bc04f9ecf2 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -2159,7 +2159,7 @@ export default function Layout(props: ParentProps) { {language.t("command.provider.connect")} diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 57ef1853d1..d917ce4c7a 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -956,13 +956,15 @@ export default function Page() { return (
-
Create a Git repository
+
{language.t("session.review.noVcs.createGit.title")}
- Track, review, and undo changes in this project + {language.t("session.review.noVcs.createGit.description")}
) diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx index 84f77ea4a3..a5263cd743 100644 --- a/packages/app/src/pages/session/composer/session-composer-region.tsx +++ b/packages/app/src/pages/session/composer/session-composer-region.tsx @@ -196,7 +196,6 @@ export function SessionComposerRegion(props: { { const n = Math.min(store.tab + 1, total()) - return `${n} of ${total()} questions` + return language.t("session.question.progress", { current: n, total: total() }) }) const last = createMemo(() => store.tab >= total() - 1) diff --git a/packages/app/src/pages/session/composer/session-todo-dock.tsx b/packages/app/src/pages/session/composer/session-todo-dock.tsx index 5500de97a4..2cd660b39f 100644 --- a/packages/app/src/pages/session/composer/session-todo-dock.tsx +++ b/packages/app/src/pages/session/composer/session-todo-dock.tsx @@ -9,6 +9,10 @@ import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough" import { Index, createEffect, createMemo, on, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { composerEnabled, composerProbe } from "@/testing/session-composer" +import { useLanguage } from "@/context/language" + +const doneToken = "\u0000done\u0000" +const totalToken = "\u0000total\u0000" function dot(status: Todo["status"]) { if (status !== "in_progress") return undefined @@ -38,11 +42,11 @@ function dot(status: Todo["status"]) { export function SessionTodoDock(props: { sessionID?: string todos: Todo[] - title: string collapseLabel: string expandLabel: string dockProgress: number }) { + const language = useLanguage() const [store, setStore] = createStore({ collapsed: false, height: 320, @@ -52,7 +56,12 @@ export function SessionTodoDock(props: { const total = createMemo(() => props.todos.length) const done = createMemo(() => props.todos.filter((todo) => todo.status === "completed").length) - const label = createMemo(() => `${done()} of ${total()} ${props.title.toLowerCase()} completed`) + const label = createMemo(() => language.t("session.todo.progress", { done: done(), total: total() })) + const progress = createMemo(() => + language + .t("session.todo.progress", { done: doneToken, total: totalToken }) + .split(/(\u0000done\u0000|\u0000total\u0000)/), + ) const active = createMemo( () => @@ -137,10 +146,17 @@ export function SessionTodoDock(props: { opacity: `${Math.max(0, Math.min(1, 1 - shut()))}`, }} > - - of - -  {props.title.toLowerCase()} completed + + {(item) => + item() === doneToken ? ( + + ) : item() === totalToken ? ( + + ) : ( + {item()} + ) + } +
{ const title = input.title ?? "" const number = input.titleNumber ?? 0 - const match = title.match(/^Terminal (\d+)$/) - const parsed = match ? Number(match[1]) : undefined - const isDefaultTitle = Number.isFinite(number) && number > 0 && Number.isFinite(parsed) && parsed === number + const isDefaultTitle = Number.isFinite(number) && number > 0 && isDefaultTerminalTitle(title, number) if (title && !isDefaultTitle) return title if (number > 0) return input.t("terminal.title.numbered", { number }) diff --git a/packages/ui/src/components/basic-tool.tsx b/packages/ui/src/components/basic-tool.tsx index 3f009f4e0f..0b2c1e1ce4 100644 --- a/packages/ui/src/components/basic-tool.tsx +++ b/packages/ui/src/components/basic-tool.tsx @@ -1,5 +1,6 @@ import { createEffect, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js" import { animate, type AnimationPlaybackControls } from "motion" +import { useI18n } from "../context/i18n" import { createStore } from "solid-js/store" import { Collapsible } from "./collapsible" import type { IconProps } from "./icon" @@ -233,12 +234,14 @@ export function GenericTool(props: { hideDetails?: boolean input?: Record }) { + const i18n = useI18n() + return ( void onNext: () => void }) { + const i18n = useI18n() + return (
props.onInput(e.currentTarget.value)} @@ -40,7 +43,7 @@ export function FileSearchBar(props: { type="button" class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none" disabled={props.count() === 0} - aria-label="Previous match" + aria-label={i18n.t("ui.fileSearch.previousMatch")} onClick={props.onPrev} > @@ -49,7 +52,7 @@ export function FileSearchBar(props: { type="button" class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none" disabled={props.count() === 0} - aria-label="Next match" + aria-label={i18n.t("ui.fileSearch.nextMatch")} onClick={props.onNext} > @@ -58,7 +61,7 @@ export function FileSearchBar(props: { diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 17572923eb..04d898134c 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -9,7 +9,8 @@ import { Tooltip } from "@opencode-ai/ui/tooltip" import { base64Encode } from "@opencode-ai/util/encode" import { getFilename } from "@opencode-ai/util/path" import { A, useNavigate, useParams } from "@solidjs/router" -import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js" +import { type Accessor, createEffect, createMemo, For, type JSX, on, onCleanup, Show } from "solid-js" +import { createStore } from "solid-js/store" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout" @@ -101,46 +102,94 @@ const SessionRow = (props: { warmPress: () => void warmFocus: () => void cancelHoverPrefetch: () => void -}): JSX.Element => ( - { - props.setHoverSession(undefined) - if (props.sidebarOpened()) return - props.clearHoverProjectSoon() - }} - > -
-
- }> - - - - -
- - -
- - 0}> -
- - +}): JSX.Element => { + const [slot, setSlot] = createStore({ + open: false, + show: false, + fade: false, + }) + + let f: number | undefined + const clear = () => { + if (f !== undefined) window.clearTimeout(f) + f = undefined + } + + onCleanup(clear) + createEffect( + on( + () => props.isWorking(), + (on, prev) => { + clear() + if (on) { + setSlot({ open: true, show: true, fade: false }) + return + } + if (prev) { + setSlot({ open: false, show: true, fade: true }) + f = window.setTimeout(() => setSlot({ show: false, fade: false }), 260) + return + } + setSlot({ open: false, show: false, fade: false }) + }, + { defer: true }, + ), + ) + + return ( + { + props.setHoverSession(undefined) + if (props.sidebarOpened()) return + props.clearHoverProjectSoon() + }} + > + 0)}> +
0, + }} + aria-hidden="true" + /> + + +
+ + + + {props.session.title} +
- - {props.session.title} - -
-
-) + + ) +} const SessionHoverPreview = (props: { mobile?: boolean @@ -204,8 +253,18 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { }) const isWorking = createMemo(() => { if (hasPermissions()) return false + const pending = (sessionStore.message[props.session.id] ?? []).findLast( + (message) => + message.role === "assistant" && + typeof (message as { time?: { completed?: unknown } }).time?.completed !== "number", + ) const status = sessionStore.session_status[props.session.id] - return status?.type === "busy" || status?.type === "retry" + return ( + pending !== undefined || + status?.type === "busy" || + status?.type === "retry" || + (status !== undefined && status.type !== "idle") + ) }) const tint = createMemo(() => { @@ -300,7 +359,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { return (
Promise language: ReturnType }): JSX.Element => ( -