From 92d4cc7de4c1c42219cf595520309459f637ee6d Mon Sep 17 00:00:00 2001 From: Paul Liu <20290410+Paulkm2006@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:08:02 +0800 Subject: [PATCH 1/6] chore: check specs --- .github/workflows/ci.yml | 13 +++++ Taskfile.yml | 2 +- build/tool-versions.env | 6 +-- .../ADR-007-wails-v3-go-migration.md | 12 ++--- .../plans/2026-08-06-ota-updater.md | 2 +- frontend/e2e/wails.spec.ts | 12 ++--- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 10 ++-- frontend/src/components/AgentManageRow.tsx | 2 +- frontend/src/components/SelectField.tsx | 2 +- frontend/src/state/TaskCenterContext.tsx | 4 +- frontend/src/state/ThemeContext.tsx | 2 +- frontend/src/state/wizardReducer.ts | 2 +- go.mod | 4 +- go.sum | 8 +-- internal/app/agent.go | 6 ++- internal/app/desktopapp.go | 6 ++- internal/app/provider.go | 20 ++++++-- internal/app/provider_test.go | 27 +++++++++- internal/app/runtime_test.go | 2 +- internal/app/settings_test.go | 4 +- internal/app/status.go | 50 +++++++++++++------ internal/app/zz_probe_test.go | 2 +- internal/binding/services.go | 4 -- internal/binding/services_test.go | 4 +- internal/catalog/catalog_test.go | 10 ---- internal/catalog/public.go | 9 ---- internal/desktopapp/desktopapp_test.go | 15 ------ internal/desktopapp/registry.go | 22 -------- internal/install/bootstrap_test.go | 2 +- internal/install/install.go | 45 ----------------- internal/install/install_test.go | 26 ---------- internal/process/process.go | 6 --- internal/process/process_test.go | 4 +- internal/profile/binding.go | 47 +++++++---------- internal/profile/binding_test.go | 29 ++++++++--- internal/profile/store.go | 35 +++++++------ internal/profile/store_test.go | 17 ++++--- internal/profile/write_test.go | 8 +-- internal/provider/client.go | 13 ----- internal/provider/client_test.go | 4 +- scripts/generate_third_party_licenses.py | 2 +- third_party/THIRD_PARTY_NOTICES.md | 2 +- third_party/manifest.json | 4 +- 44 files changed, 221 insertions(+), 287 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60e55fc..c30d73d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,11 @@ jobs: steps: - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + - uses: pnpm/action-setup@v6 with: version: 11.17.0 @@ -110,3 +115,11 @@ jobs: - name: Build working-directory: frontend run: pnpm run build + + - name: Install browser + working-directory: frontend + run: pnpm exec playwright install --with-deps chromium + + - name: Wails E2E + working-directory: frontend + run: pnpm run test:e2e diff --git a/Taskfile.yml b/Taskfile.yml index 162ed97..f9d9278 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -8,7 +8,7 @@ vars: # cross-compile (only windows supports cross-compilation, see build/*/Taskfile.yml). GOOS: '{{.GOOS | default OS}}' WAILS_MODULE: github.com/wailsapp/wails/v3 - WAILS_VERSION: v3.0.0-beta.3 + WAILS_VERSION: v3.0.0-beta.4 # Pinned Wails CLI (see build/tool-versions.env); never rely on a global wails3. WAILS3: go run {{.WAILS_MODULE}}/cmd/wails3@{{.WAILS_VERSION}} diff --git a/build/tool-versions.env b/build/tool-versions.env index f34e638..299c500 100644 --- a/build/tool-versions.env +++ b/build/tool-versions.env @@ -2,7 +2,7 @@ # and binding-diff checks have been rerun on all four supported targets. GO_VERSION=1.26 NODE_VERSION=22 -WAILS_VERSION=v3.0.0-beta.3 -WAILS_CLI_VERSION=v3.0.0-beta.3 -WAILS_RUNTIME_VERSION=3.0.0-alpha2.117 +WAILS_VERSION=v3.0.0-beta.4 +WAILS_CLI_VERSION=v3.0.0-beta.4 +WAILS_RUNTIME_VERSION=3.0.0-beta.1 TASK_VERSION=v3.40.1-patched3 diff --git a/docs/decisions/ADR-007-wails-v3-go-migration.md b/docs/decisions/ADR-007-wails-v3-go-migration.md index d3fd326..0e82859 100644 --- a/docs/decisions/ADR-007-wails-v3-go-migration.md +++ b/docs/decisions/ADR-007-wails-v3-go-migration.md @@ -7,16 +7,16 @@ ## Context The migration plan called for a Wails v3 desktop shell, a transport-independent -Go core, and a separate headless CLI. Wails v3 is still Alpha, so the shipped -channel remains an unsigned technical preview. +Go core, and a separate headless CLI. Wails v3 is still pre-release, so the +shipped channel remains an unsigned technical preview. ## Decision 1. The migration line uses Go 1.26+ and pins Wails v3 to - `v3.0.0-beta.3` for the initial native spike. The matching CLI uses the - same module version. The browser runtime candidate is pinned separately to - `3.0.0-alpha2.117`, the version shipped by that Wails module and present in - the frontend lockfile used by the production bundle. + `v3.0.0-beta.4` for the initial native spike. The matching CLI uses the + same module version. The browser runtime is pinned separately to + `3.0.0-beta.1`, the version embedded in that Wails module and present in the + frontend lockfile used by the production bundle. 2. `agents.lock.json` remains the only hand-edited Agent catalog source. The root Go package embeds that file once, and `internal/catalog` parses the embedded bytes for both the CLI and desktop shell. diff --git a/docs/superpowers/plans/2026-08-06-ota-updater.md b/docs/superpowers/plans/2026-08-06-ota-updater.md index 586248e..62799f7 100644 --- a/docs/superpowers/plans/2026-08-06-ota-updater.md +++ b/docs/superpowers/plans/2026-08-06-ota-updater.md @@ -6,7 +6,7 @@ **Architecture:** Wails' `pkg/updater` owns release comparison, GitHub asset selection, checksum verification, archive extraction, staging, swapping, and relaunch. A narrow `internal/binding.UpdateService` adapts that concrete updater to three cancellable Wails methods. A frontend-only `AppUpdater` checks once at startup, uses `@wailsio/runtime`'s native `Dialogs.Question` for consent, and drives the existing task-center state and progress event pipeline. -**Tech Stack:** Go 1.26, Wails v3.0.0-beta.3 `pkg/updater`, GitHub Releases API, React 19, TypeScript, Vitest, pnpm, GitHub Actions. +**Tech Stack:** Go 1.26, Wails v3.0.0-beta.4 `pkg/updater`, GitHub Releases API, React 19, TypeScript, Vitest, pnpm, GitHub Actions. --- diff --git a/frontend/e2e/wails.spec.ts b/frontend/e2e/wails.spec.ts index c8125b1..0a6996c 100644 --- a/frontend/e2e/wails.spec.ts +++ b/frontend/e2e/wails.spec.ts @@ -66,7 +66,7 @@ test("every sidebar control at the bottom is actually clickable", async ({ page test("a machine with no OneAgent state opens onboarding from the landing route", async ({ page }) => { await page.goto("/#/"); - await expect(page.getByRole("heading", { name: "选择命令行 Agent" })).toBeVisible(); + await expect(page.getByRole("heading", { name: "选择 Agent", level: 1 })).toBeVisible(); }); test("onboarding installs one Agent end to end and writes its Profile", async ({ page }) => { @@ -88,10 +88,10 @@ test("onboarding installs one Agent end to end and writes its Profile", async ({ await expect(page.getByTestId("provider-ppio")).toContainText("已保存 Key"); await page.goto("/#/overview"); - await expect(page.getByText("尚未安装任何 Agent")).toBeVisible(); - await page.getByRole("button", { name: "安装 Agent" }).click(); + await expect(page.getByText("尚未安装任何命令行 Agent")).toBeVisible(); + await page.getByRole("button", { name: "安装命令行 Agent" }).click(); - await expect(page.getByRole("heading", { name: "选择命令行 Agent" })).toBeVisible(); + await expect(page.getByRole("heading", { name: "选择 Agent", level: 1 })).toBeVisible(); await page.getByLabel("选择 Codex").check(); await page.getByRole("button", { name: "继续" }).click(); @@ -119,8 +119,8 @@ test("onboarding installs one Agent end to end and writes its Profile", async ({ // The Profile the install wrote is editable from the Agent row, without any // credential or endpoint fields on the edit page. - await page.getByRole("link", { name: "编辑配置" }).click(); - await expect(page.getByText("团队默认")).toBeVisible(); + await page.getByRole("link", { name: "配置", exact: true }).click(); + await expect(page.getByRole("heading", { name: "选择配置模板" })).toBeVisible(); await expect(page.getByLabel("API Key")).toHaveCount(0); await expect(page.getByText("Base URL")).toHaveCount(0); expect(bindingMethodIDs.size).toBeGreaterThanOrEqual(3); diff --git a/frontend/package.json b/frontend/package.json index 59b559b..5c8099c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,7 @@ "test:e2e": "playwright test" }, "dependencies": { - "@wailsio/runtime": "3.0.0-alpha2.117", + "@wailsio/runtime": "3.0.0-beta.1", "lucide-react": "1.25.0", "react": "19.2.8", "react-dom": "19.2.8", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 8415685..904ce2d 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@wailsio/runtime': - specifier: 3.0.0-alpha2.117 - version: 3.0.0-alpha2.117 + specifier: 3.0.0-beta.1 + version: 3.0.0-beta.1 lucide-react: specifier: 1.25.0 version: 1.25.0(react@19.2.8) @@ -547,8 +547,8 @@ packages: '@vitest/utils@4.1.10': resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} - '@wailsio/runtime@3.0.0-alpha2.117': - resolution: {integrity: sha512-RZr6cncIXjdTbn2IqJ6AZXPm9WooaZEsNlNfaUH+Ru/YlH5sKDDeRdWpbcvsgoMLh4AJ+O8aBzZEiTYWDipGxw==} + '@wailsio/runtime@3.0.0-beta.1': + resolution: {integrity: sha512-y0FFOONsaWyRbQItjERV9sjUFmO4M5XGrHE8aX+rdYQG3YclTfYkMrpalMQcdxEh1CUrrpyvLabHt/2zCFnv+Q==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -1473,7 +1473,7 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.1 - '@wailsio/runtime@3.0.0-alpha2.117': {} + '@wailsio/runtime@3.0.0-beta.1': {} ansi-regex@5.0.1: {} diff --git a/frontend/src/components/AgentManageRow.tsx b/frontend/src/components/AgentManageRow.tsx index 095a5e9..fa52774 100644 --- a/frontend/src/components/AgentManageRow.tsx +++ b/frontend/src/components/AgentManageRow.tsx @@ -50,7 +50,7 @@ export function isBehind(installed: string, locked: string): boolean { return compareVersions(installed, locked) < 0; } -export function versionNote(status: AgentStatus, t: Translate = sourceTranslate): { text: string; behind: boolean } | null { +function versionNote(status: AgentStatus, t: Translate = sourceTranslate): { text: string; behind: boolean } | null { if (!status.installed || !status.version) return null; if (!status.lockedVersion || status.version === status.lockedVersion) { // Being current is the normal case and needs no words: the bare version is diff --git a/frontend/src/components/SelectField.tsx b/frontend/src/components/SelectField.tsx index dbae470..270cb8f 100644 --- a/frontend/src/components/SelectField.tsx +++ b/frontend/src/components/SelectField.tsx @@ -1,7 +1,7 @@ import { Check, ChevronDown } from "lucide-react"; import { useEffect, useId, useLayoutEffect, useRef, useState } from "react"; -export interface SelectOption { +interface SelectOption { value: string; label: string; disabled?: boolean; diff --git a/frontend/src/state/TaskCenterContext.tsx b/frontend/src/state/TaskCenterContext.tsx index aa2ede3..4dad288 100644 --- a/frontend/src/state/TaskCenterContext.tsx +++ b/frontend/src/state/TaskCenterContext.tsx @@ -20,7 +20,7 @@ export interface TaskProgress { } export type TaskKind = "install" | "update" | "download"; -export type TaskState = "running" | "success" | "failure" | "cancelled"; +type TaskState = "running" | "success" | "failure" | "cancelled"; export interface TaskOutcome { kind: Exclude; @@ -76,7 +76,7 @@ function taskLockKey(kind: TaskKind, target: string): string { } /** HashRouter is used by the desktop shell; this also works in browser tests. */ -export function taskRoute(): string { +function taskRoute(): string { if (typeof window === "undefined") return "/overview"; const hash = window.location.hash; return hash.startsWith("#") && hash.slice(1) ? hash.slice(1) : `${window.location.pathname}${window.location.search}`; diff --git a/frontend/src/state/ThemeContext.tsx b/frontend/src/state/ThemeContext.tsx index 0acaa4a..d8e23a8 100644 --- a/frontend/src/state/ThemeContext.tsx +++ b/frontend/src/state/ThemeContext.tsx @@ -37,7 +37,7 @@ export function storedPreference(): ThemePreference { * tokens.css decide. Exported so the pre-hydration script and the provider apply * it exactly the same way. */ -export function applyPreference(preference: ThemePreference): void { +function applyPreference(preference: ThemePreference): void { const root = document.documentElement; root.classList.toggle("theme-dark", preference === "dark"); root.classList.toggle("theme-light", preference === "light"); diff --git a/frontend/src/state/wizardReducer.ts b/frontend/src/state/wizardReducer.ts index f91e058..2aa1124 100644 --- a/frontend/src/state/wizardReducer.ts +++ b/frontend/src/state/wizardReducer.ts @@ -10,7 +10,7 @@ import type { import { byProviderCreatedAt } from "./ranking"; export type AsyncState = "idle" | "loading" | "success" | "error"; -export type SetupKind = "cli" | "desktop"; +type SetupKind = "cli" | "desktop"; export interface WizardState { status: StatusResponse | null; diff --git a/go.mod b/go.mod index 1a5cda9..3883a06 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.5 require ( github.com/pelletier/go-toml/v2 v2.4.3 - github.com/wailsapp/wails/v3 v3.0.0-beta.3 + github.com/wailsapp/wails/v3 v3.0.0-beta.4 ) require ( @@ -15,6 +15,6 @@ require ( github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.24 // indirect - golang.org/x/mod v0.35.0 // indirect + golang.org/x/mod v0.38.0 // indirect golang.org/x/sys v0.47.0 // indirect ) diff --git a/go.sum b/go.sum index 2a94269..1de92cb 100644 --- a/go.sum +++ b/go.sum @@ -26,10 +26,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/wailsapp/wails/v3 v3.0.0-beta.3 h1:BrcZunEBVucncRx+xgkk9TzlXU4qc0ygJuEhKAAGaeA= -github.com/wailsapp/wails/v3 v3.0.0-beta.3/go.mod h1:BzATbK71VFikMMMCo434wAi0QcaI03P+xeaWgDvQvjw= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +github.com/wailsapp/wails/v3 v3.0.0-beta.4 h1:Kv5ywwZDMB0SgA1zhLM4fImf5ZsGWATTkoAQ3DS/pB8= +github.com/wailsapp/wails/v3 v3.0.0-beta.4/go.mod h1:BzATbK71VFikMMMCo434wAi0QcaI03P+xeaWgDvQvjw= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= diff --git a/internal/app/agent.go b/internal/app/agent.go index 498988b..12d5859 100644 --- a/internal/app/agent.go +++ b/internal/app/agent.go @@ -111,7 +111,11 @@ func (u *UseCases) activateAgentLocked(ctx context.Context, options ActivateAgen protocol := provider.ProtocolForAdapter(agent.ConfigAdapter) if profileID != "" { - for _, saved := range u.profiles.List() { + profiles, err := u.profiles.List() + if err != nil { + return ActivateAgentResult{}, err + } + for _, saved := range profiles { if saved.ID == profileID && saved.Protocol != "" && saved.Protocol != protocol { return ActivateAgentResult{}, oneerrors.New(oneerrors.InvalidRequest, fmt.Sprintf("Profile %s uses %s API mode, but %s requires %s", profileID, saved.Protocol, agentID, protocol)) } diff --git a/internal/app/desktopapp.go b/internal/app/desktopapp.go index 6d6ef08..d684928 100644 --- a/internal/app/desktopapp.go +++ b/internal/app/desktopapp.go @@ -140,7 +140,11 @@ func (u *UseCases) ConfigureDesktopAgent(ctx context.Context, agentID, profileID } definition, _ := desktopapp.DefinitionFor(agentID) var selected profileStore.Profile - for _, candidate := range u.profiles.List() { + profiles, err := u.profiles.List() + if err != nil { + return DesktopAgentProfileResult{}, err + } + for _, candidate := range profiles { if candidate.ID == profileID { selected = candidate break diff --git a/internal/app/provider.go b/internal/app/provider.go index 274e734..5cc5bc3 100644 --- a/internal/app/provider.go +++ b/internal/app/provider.go @@ -156,17 +156,23 @@ func (u *UseCases) SaveProvider(ctx context.Context, entry provider.Entry) (Save before.BaseURL == saved.BaseURL && before.APIKey == saved.APIKey { return result, nil } - result.Reapplied, result.Failures = u.reapplyProviderLocked(ctx, saved) + result.Reapplied, result.Failures, err = u.reapplyProviderLocked(ctx, saved) + if err != nil { + return result, err + } return result, nil } // reapplyProviderLocked rewrites each configured Agent that points at this // Provider, keeping its own model and Profile reference. Callers must hold // writeMu. -func (u *UseCases) reapplyProviderLocked(ctx context.Context, target provider.Entry) ([]string, map[string]string) { +func (u *UseCases) reapplyProviderLocked(ctx context.Context, target provider.Entry) ([]string, map[string]string, error) { var reapplied []string var failures map[string]string - bindings := u.profiles.ListAgentBindings() + bindings, err := u.profiles.ListAgentBindings() + if err != nil { + return nil, nil, err + } agentIDs := make([]string, 0, len(bindings)) for agentID := range bindings { agentIDs = append(agentIDs, agentID) @@ -222,7 +228,7 @@ func (u *UseCases) reapplyProviderLocked(ctx context.Context, target provider.En } reapplied = append(reapplied, agentID) } - return reapplied, failures + return reapplied, failures, nil } func (u *UseCases) DeleteProvider(ctx context.Context, providerID string) error { @@ -235,7 +241,11 @@ func (u *UseCases) DeleteProvider(ctx context.Context, providerID string) error u.writeMu.Lock() defer u.writeMu.Unlock() var users []string - for agentID, binding := range u.catalogAgentBindings(false) { + bindings, err := u.catalogAgentBindings(false) + if err != nil { + return err + } + for agentID, binding := range bindings { if binding.Provider == strings.TrimSpace(providerID) { users = append(users, agentID) } diff --git a/internal/app/provider_test.go b/internal/app/provider_test.go index 2cb3b58..76f99fb 100644 --- a/internal/app/provider_test.go +++ b/internal/app/provider_test.go @@ -249,11 +249,36 @@ func TestDeleteProfileRejectsBoundAgents(t *testing.T) { if _, err := core.profiles.ProfilePath("team"); err != nil { t.Fatal(err) } - if len(core.profiles.List()) != 1 { + profiles, listErr := core.profiles.List() + if listErr != nil || len(profiles) != 1 { t.Fatal("in-use Profile was deleted") } } +func TestDeleteProfileStopsOnCorruptAgentBinding(t *testing.T) { + home := t.TempDir() + core := activationCore(t, home, provider.NewClient(nil), "linux") + if _, err := core.SaveProfile(context.Background(), SaveProfileOptions{ + ID: "team", Provider: "ppio", Model: "model-a", ConfigMode: "provider", + }); err != nil { + t.Fatal(err) + } + bindingPath, _ := core.profiles.AgentBindingPath("codex") + if err := os.MkdirAll(filepath.Dir(bindingPath), 0o700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(bindingPath, []byte("{"), 0o600); err != nil { + t.Fatal(err) + } + if err := core.DeleteProfile(context.Background(), "team"); err == nil { + t.Fatal("corrupt Agent binding did not stop Profile deletion") + } + profilePath, _ := core.profiles.ProfilePath("team") + if _, err := os.Stat(profilePath); err != nil { + t.Fatalf("Profile was deleted: %v", err) + } +} + func TestDeleteIgnoresStaleBindingWithoutAgentConfig(t *testing.T) { home := t.TempDir() core := activationCore(t, home, provider.NewClient(nil), "linux") diff --git a/internal/app/runtime_test.go b/internal/app/runtime_test.go index 8419b24..7f32a9b 100644 --- a/internal/app/runtime_test.go +++ b/internal/app/runtime_test.go @@ -170,7 +170,7 @@ func TestManagedRuntimeIsReusedRatherThanReinstalled(t *testing.T) { } // EnsureRuntime must return the existing tree without downloading. - runtime := install.NewRuntime(home, platform.For("darwin", "arm64"), process.New(map[string]string{"HOME": home, "PATH": filepath.Join(home, "empty")}), map[string]string{"HOME": home}) + runtime := install.NewRuntime(home, platform.For("darwin", "arm64"), process.OSRunner{Env: map[string]string{"HOME": home, "PATH": filepath.Join(home, "empty")}}, map[string]string{"HOME": home}) updated, installed, err := install.EnsureRuntime(context.Background(), runtime, core.httpDoer, "node", entry, install.RuntimeOptions{}) if err != nil || installed { t.Fatalf("existing tree triggered a download: %v, %v", installed, err) diff --git a/internal/app/settings_test.go b/internal/app/settings_test.go index 3c8b2bd..2315ec0 100644 --- a/internal/app/settings_test.go +++ b/internal/app/settings_test.go @@ -31,7 +31,7 @@ func settingsCore(t *testing.T, home string, locale ...string) *UseCases { Home: home, Platform: platform.For("linux", "x64"), Environment: environment, - Runner: process.New(environment), + Runner: process.OSRunner{Env: environment}, }) } @@ -260,7 +260,7 @@ func TestTheRegionalDefaultReachesTheDownload(t *testing.T) { func TestTheRegionProbeRunsOnlyOnce(t *testing.T) { home := t.TempDir() environment := map[string]string{"HOME": home, "PATH": filepath.Join(home, "empty")} - counter := &countingRunner{Runner: process.New(environment)} + counter := &countingRunner{Runner: process.OSRunner{Env: environment}} core := NewUseCases(StatusOptions{ // macOS needs a lookup, so this exercises the cached subprocess path. Home: home, Platform: platform.For("darwin", "arm64"), diff --git a/internal/app/status.go b/internal/app/status.go index af88dfb..074c489 100644 --- a/internal/app/status.go +++ b/internal/app/status.go @@ -280,7 +280,10 @@ func (u *UseCases) GetStatus(ctx context.Context) (StatusResponse, error) { runtimes, runtimeLookup := u.runtimeCapability(ctx) agentLookup := runtimeLookup.lookup statuses := make(map[string]AgentStatus, len(manifest.Agents)) - bindings := u.profiles.ListAgentBindings() + bindings, err := u.profiles.ListAgentBindings() + if err != nil { + return StatusResponse{}, err + } latestVersions := u.latestAgentVersions(ctx, manifest, agentLookup) installedVersions := u.installedVersions(ctx, manifest, agentLookup) for _, id := range catalog.AgentIDs(manifest) { @@ -349,7 +352,10 @@ func (u *UseCases) GetStatus(ctx context.Context) (StatusResponse, error) { if err != nil { return StatusResponse{}, err } - profiles, activeProfile, environment, environmentError := u.profileStatus(ctx) + profiles, activeProfile, environment, environmentError, err := u.profileStatus(ctx) + if err != nil { + return StatusResponse{}, err + } desktopAgents := u.desktopAgentStatuses(ctx) return StatusResponse{ APIVersion: 1, @@ -470,7 +476,7 @@ func (u *UseCases) ListProfiles(ctx context.Context) ([]ProfileSummary, error) { if err := ctx.Err(); err != nil { return nil, oneerrors.New(oneerrors.Timeout, "Profile request was cancelled", oneerrors.WithRetryable(true), oneerrors.WithCause(err)) } - return u.profileSummaries(), nil + return u.profileSummaries() } // ListAgentBindings returns the public per-Agent routing records used by the @@ -482,7 +488,7 @@ func (u *UseCases) ListAgentBindings(ctx context.Context) (map[string]profileSto if err := contextError(ctx, "Agent listing request was cancelled"); err != nil { return nil, err } - return u.profiles.ListAgentBindings(), nil + return u.profiles.ListAgentBindings() } type SaveProfileOptions struct { @@ -549,7 +555,11 @@ func (u *UseCases) DeleteProfile(ctx context.Context, id string) error { defer u.writeMu.Unlock() id = strings.TrimSpace(id) var users []string - for agentID, binding := range u.catalogAgentBindings(true) { + bindings, err := u.catalogAgentBindings(true) + if err != nil { + return err + } + for agentID, binding := range bindings { if binding.ProfileRef == id { users = append(users, agentID) } @@ -563,27 +573,34 @@ func (u *UseCases) DeleteProfile(ctx context.Context, id string) error { // catalogAgentBindings mirrors the management pages' user lists. Profiles only // show auto-configured Agents; Providers show every catalog Agent. -func (u *UseCases) catalogAgentBindings(autoOnly bool) map[string]profileStore.AgentBinding { +func (u *UseCases) catalogAgentBindings(autoOnly bool) (map[string]profileStore.AgentBinding, error) { manifest, err := catalog.LoadEmbedded() if err != nil { - return map[string]profileStore.AgentBinding{} + return nil, err + } + bindings, err := u.profiles.ListAgentBindings() + if err != nil { + return nil, err } result := map[string]profileStore.AgentBinding{} - for agentID, binding := range u.profiles.ListAgentBindings() { + for agentID, binding := range bindings { agent, ok := manifest.Agents[agentID] if !ok || (autoOnly && agent.ConfigMode != "auto") { continue } result[agentID] = binding } - return result + return result, nil } -func (u *UseCases) profileStatus(ctx context.Context) ([]ProfileSummary, *string, any, *string) { +func (u *UseCases) profileStatus(ctx context.Context) ([]ProfileSummary, *string, any, *string, error) { u.writeMu.Lock() defer u.writeMu.Unlock() active := u.profiles.LoadActiveContext(ctx) - profiles := u.profileSummaries() + profiles, err := u.profileSummaries() + if err != nil { + return nil, nil, nil, nil, err + } var activeID *string if active.ID != "" { id := active.ID @@ -598,16 +615,19 @@ func (u *UseCases) profileStatus(ctx context.Context) ([]ProfileSummary, *string errorText := active.Error environmentError = &errorText } - return profiles, activeID, environment, environmentError + return profiles, activeID, environment, environmentError, nil } -func (u *UseCases) profileSummaries() []ProfileSummary { - stored := u.profiles.List() +func (u *UseCases) profileSummaries() ([]ProfileSummary, error) { + stored, err := u.profiles.List() + if err != nil { + return nil, err + } result := make([]ProfileSummary, 0, len(stored)) for _, item := range stored { result = append(result, profileSummary(item)) } - return result + return result, nil } func profileSummary(item profileStore.Profile) ProfileSummary { diff --git a/internal/app/zz_probe_test.go b/internal/app/zz_probe_test.go index ca72751..ae9bea7 100644 --- a/internal/app/zz_probe_test.go +++ b/internal/app/zz_probe_test.go @@ -23,7 +23,7 @@ func TestProbeRealMachineAfterFix(t *testing.T) { info := platform.Current() home := t.TempDir() env := map[string]string{"HOME": home, "PATH": filepath.Join(home, "empty")} - core := NewUseCases(StatusOptions{Home: home, Platform: info, Environment: env, Runner: process.New(env)}) + core := NewUseCases(StatusOptions{Home: home, Platform: info, Environment: env, Runner: process.OSRunner{Env: env}}) cancelled, cancel := context.WithCancel(context.Background()) cancel() diff --git a/internal/binding/services.go b/internal/binding/services.go index 474b728..89af5e4 100644 --- a/internal/binding/services.go +++ b/internal/binding/services.go @@ -286,10 +286,6 @@ func (s *AgentService) Update(ctx context.Context, request UpdateRequest) (app.A return s.core.UpdateAgent(ctx, request.AgentID) } -func NewAgentService(core *app.UseCases) *AgentService { - return &AgentService{core: core} -} - func (s *AgentService) Install(ctx context.Context, request InstallRequest) (InstallResponse, error) { if err := contextError(ctx); err != nil { return InstallResponse{}, err diff --git a/internal/binding/services_test.go b/internal/binding/services_test.go index cf53b23..8b71641 100644 --- a/internal/binding/services_test.go +++ b/internal/binding/services_test.go @@ -232,7 +232,7 @@ func TestAgentServiceActivatesThroughGoUseCase(t *testing.T) { Platform: platform.For("linux", "amd64"), Lookup: func(string) (string, bool) { return "", false }, }) - service := NewAgentService(core) + service := &AgentService{core: core} response, err := service.Activate(context.Background(), ActivateRequest{ AgentID: "codex", Provider: "ppio", @@ -261,7 +261,7 @@ func TestAgentServiceInstallsThroughGoUseCase(t *testing.T) { Platform: platform.For("linux", "amd64"), Lookup: func(string) (string, bool) { return "", false }, }) - service := NewAgentService(core) + service := &AgentService{core: core} response, err := service.Install(context.Background(), InstallRequest{ Agents: []string{"codex"}, Provider: "ppio", diff --git a/internal/catalog/catalog_test.go b/internal/catalog/catalog_test.go index cce0542..e338c6d 100644 --- a/internal/catalog/catalog_test.go +++ b/internal/catalog/catalog_test.go @@ -3,7 +3,6 @@ package catalog import ( "encoding/json" "reflect" - "sort" "testing" ) @@ -55,15 +54,6 @@ func TestEmbeddedProvidersMatchCurrentCatalogContract(t *testing.T) { t.Fatalf("provider %q is incomplete: %#v", id, provider) } } - ids := ProviderIDs() - wantIDs := make([]string, 0, len(manifest.Providers)) - for id := range manifest.Providers { - wantIDs = append(wantIDs, id) - } - sort.Strings(wantIDs) - if !reflect.DeepEqual(ids, wantIDs) { - t.Fatalf("ProviderIDs() = %v, want %v", ids, wantIDs) - } if got := FallbackProbeModel("unknown"); got != manifest.DefaultFallbackProbeModel { t.Fatalf("unknown fallback = %q, want %q", got, manifest.DefaultFallbackProbeModel) } diff --git a/internal/catalog/public.go b/internal/catalog/public.go index a73cdc4..2d90dab 100644 --- a/internal/catalog/public.go +++ b/internal/catalog/public.go @@ -76,15 +76,6 @@ func ProviderByID(providerID string) (Provider, bool) { return provider, true } -func ProviderIDs() []string { - ids := make([]string, 0, len(providerDefinitions)) - for id := range providerDefinitions { - ids = append(ids, id) - } - sort.Strings(ids) - return ids -} - func Groups() []Group { return append([]Group(nil), groups...) } diff --git a/internal/desktopapp/desktopapp_test.go b/internal/desktopapp/desktopapp_test.go index 537b81f..e428892 100644 --- a/internal/desktopapp/desktopapp_test.go +++ b/internal/desktopapp/desktopapp_test.go @@ -604,21 +604,6 @@ func TestCompareVersionHandlesMissingComponents(t *testing.T) { } } -func TestProfileAgentIDKeepsChatGPTOnCodexAndScopesOtherApps(t *testing.T) { - if got := ProfileAgentID(ChatGPTDesktopID); got != CodexAgentID || !SharesProfile(ChatGPTDesktopID) { - t.Fatalf("ChatGPT profile mapping = %q, shared=%v", got, SharesProfile(ChatGPTDesktopID)) - } - if got := ProfileAgentID(" " + ChatGPTDesktopID + " "); got != CodexAgentID || !SharesProfile(" "+ChatGPTDesktopID+" ") { - t.Fatalf("trimmed ChatGPT profile mapping = %q, shared=%v", got, SharesProfile(" "+ChatGPTDesktopID+" ")) - } - if got := ProfileAgentID("workbuddy"); got != "workbuddy" || SharesProfile("workbuddy") { - t.Fatalf("other desktop mapping = %q, shared=%v", got, SharesProfile("workbuddy")) - } - if SharesProfile(" workbuddy ") { - t.Fatal("whitespace around a non-shared desktop ID changed its ownership") - } -} - func TestVerifyMacOSAppRequiresExpectedTeamAndNotarization(t *testing.T) { runner := &scriptedRunner{results: []process.Result{ {ExitCode: 0}, diff --git a/internal/desktopapp/registry.go b/internal/desktopapp/registry.go index cbaf42c..409286a 100644 --- a/internal/desktopapp/registry.go +++ b/internal/desktopapp/registry.go @@ -69,14 +69,6 @@ func Definitions() []Definition { return result } -func IDs() []string { - result := make([]string, 0, len(implementations)) - for _, agent := range implementations { - result = append(result, agent.ID) - } - return result -} - func DefinitionFor(agentID string) (Definition, bool) { agent, ok := implementationFor(agentID) if !ok { @@ -85,20 +77,6 @@ func DefinitionFor(agentID string) (Definition, bool) { return agent.Definition, true } -func ProfileAgentID(agentID string) string { - agentID = strings.TrimSpace(agentID) - if definition, ok := DefinitionFor(agentID); ok && definition.ProfileAgentID != "" { - return definition.ProfileAgentID - } - return agentID -} - -func SharesProfile(agentID string) bool { - agentID = strings.TrimSpace(agentID) - definition, ok := DefinitionFor(agentID) - return ok && definition.ProfileAgentID != "" && definition.ProfileAgentID != agentID -} - func implementationFor(agentID string) (implementation, bool) { agentID = strings.TrimSpace(agentID) for _, agent := range implementations { diff --git a/internal/install/bootstrap_test.go b/internal/install/bootstrap_test.go index 3879416..2a49466 100644 --- a/internal/install/bootstrap_test.go +++ b/internal/install/bootstrap_test.go @@ -137,7 +137,7 @@ func digestOf(data []byte) string { func bootstrapRuntime(t *testing.T, home, osID string) Runtime { t.Helper() - runner := process.New(map[string]string{"HOME": home, "PATH": filepath.Join(home, "empty")}) + runner := process.OSRunner{Env: map[string]string{"HOME": home, "PATH": filepath.Join(home, "empty")}} return NewRuntime(home, platform.For(osID, "arm64"), runner, runner.Env) } diff --git a/internal/install/install.go b/internal/install/install.go index ba3a193..09f41c4 100644 --- a/internal/install/install.go +++ b/internal/install/install.go @@ -75,51 +75,6 @@ func managedNPM(runtime Runtime, npm string) bool { return err == nil && relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) } -// ResolveAiderPython312 reports an existing Python 3.12 when the host has one. -// It is retained for the CLI's diagnostics; installation itself no longer needs -// it because uv provisions the interpreter. -func ResolveAiderPython312(ctx context.Context, runtime Runtime) (string, error) { - if err := checkContext(ctx); err != nil { - return "", err - } - if runtime.Runner == nil { - return "", prerequisiteError("An existing Python 3.12 installation is required for Aider; OneAgent will not download Python automatically") - } - if executable, ok := runtime.Runner.LookPath("python3.12"); ok && executable != "" { - return executable, nil - } - for _, command := range []string{"python3", "python"} { - executable, ok := runtime.Runner.LookPath(command) - if !ok || executable == "" { - continue - } - result, err := runtime.command(ctx, []string{executable, "--version"}, nil, VersionCommandTimeout) - if err != nil || result.ExitCode != 0 { - if isContextError(err) { - return "", timeoutError("Checking for Python 3.12 was cancelled", err) - } - continue - } - version := VersionFromOutput(result.Stdout + "\n" + result.Stderr) - if strings.HasPrefix(version, "3.12.") { - return executable, nil - } - } - if runtime.Platform.OS == "windows" { - if launcher, ok := runtime.Runner.LookPath("py"); ok && launcher != "" { - result, err := runtime.command(ctx, []string{launcher, "-3.12", "--version"}, nil, VersionCommandTimeout) - if err != nil { - if isContextError(err) { - return "", timeoutError("Checking for Python 3.12 was cancelled", err) - } - } else if result.ExitCode == 0 { - return "3.12", nil - } - } - } - return "", prerequisiteError("An existing Python 3.12 installation is required for Aider; OneAgent will not download Python automatically") -} - func ResolveRegistry(value string) (string, error) { official := officialRegistry() if value == "" { diff --git a/internal/install/install_test.go b/internal/install/install_test.go index 9e8ea17..e2764e7 100644 --- a/internal/install/install_test.go +++ b/internal/install/install_test.go @@ -167,24 +167,6 @@ func TestInstallAgentSupportsAiderRuntimeBoundary(t *testing.T) { if !strings.HasSuffix(installEnv["UV_TOOL_BIN_DIR"], "/.oneagent/runtimes/global/bin") { t.Fatalf("uv tool bin dir = %q", installEnv["UV_TOOL_BIN_DIR"]) } - - pythonRunner := &fakeInstallRunner{paths: map[string]string{"python3": "/fake/python3"}} - pythonRunner.run = func(argv []string, _ map[string]string) (process.Result, error) { - return process.Result{Args: argv, ExitCode: 0, Stdout: "Python 3.12.9"}, nil - } - pythonRuntime := runtimeForInstall(pythonRunner, "linux", nil) - if got, err := ResolveAiderPython312(context.Background(), pythonRuntime); err != nil || got != "/fake/python3" { - t.Fatalf("python3 resolution = %q, %v", got, err) - } - - windowsRunner := &fakeInstallRunner{paths: map[string]string{"py": "py.exe"}} - windowsRunner.run = func(argv []string, _ map[string]string) (process.Result, error) { - return process.Result{Args: argv, ExitCode: 0, Stdout: "Python 3.12.7"}, nil - } - windowsRuntime := runtimeForInstall(windowsRunner, "windows", nil) - if got, err := ResolveAiderPython312(context.Background(), windowsRuntime); err != nil || got != "3.12" { - t.Fatalf("py launcher resolution = %q, %v", got, err) - } } func TestInstallPrerequisitesAndFailuresAreStableAndRedacted(t *testing.T) { @@ -221,14 +203,6 @@ func TestInstallPrerequisitesAndFailuresAreStableAndRedacted(t *testing.T) { } } -func TestResolveAiderRuntimeMissingIsPrerequisiteError(t *testing.T) { - runner := &fakeInstallRunner{paths: map[string]string{}} - _, err := ResolveAiderPython312(context.Background(), runtimeForInstall(runner, "linux", nil)) - if err == nil || oneerrors.As(err).Code != oneerrors.PrerequisiteMissing { - t.Fatalf("missing Python error = %v", err) - } -} - func TestInstallerFailureDetailLimitsLinesAndLength(t *testing.T) { result := process.Result{Stderr: "one\ntwo\nthree\nfour\n" + strings.Repeat("x", 700)} detail := installerFailureDetail(result, nil) diff --git a/internal/process/process.go b/internal/process/process.go index b0e4447..4f4cfce 100644 --- a/internal/process/process.go +++ b/internal/process/process.go @@ -176,12 +176,6 @@ func (r OSRunner) WithEnvironment(env map[string]string) Runner { return r } -func New(env map[string]string) OSRunner { - values := make(map[string]string, len(env)) - maps.Copy(values, env) - return OSRunner{Env: values} -} - // LookPath resolves a command against this runner's own PATH rather than the // OneAgent process PATH. That distinction is what makes a runtime installed // into a private directory usable: Run() passes r.Env to the child, so a lookup diff --git a/internal/process/process_test.go b/internal/process/process_test.go index 1a8b1bc..f98ea8d 100644 --- a/internal/process/process_test.go +++ b/internal/process/process_test.go @@ -59,10 +59,10 @@ func helperRunner(t *testing.T) OSRunner { lands in the captured output and in the listener — failing assertions about what the command produced for a reason that has nothing to do with the runner. Giving the child a scratch directory keeps its stderr its own. */ - runner := New(map[string]string{ + runner := OSRunner{Env: map[string]string{ "ONEAGENT_PROCESS_HELPER": "1", "GOCOVERDIR": t.TempDir(), - }) + }} runner.Lookup = func(command string) (string, bool) { if command == "helper" { return path, true diff --git a/internal/profile/binding.go b/internal/profile/binding.go index 84da064..4f4b62b 100644 --- a/internal/profile/binding.go +++ b/internal/profile/binding.go @@ -31,17 +31,6 @@ type BindingWriteRequest struct { ProfileRef string } -type storedBinding struct { - SchemaVersion int `json:"schema_version"` - AgentID string `json:"agent_id"` - Provider string `json:"provider"` - BaseURL string `json:"base_url"` - Model string `json:"model"` - ProfileRef string `json:"profile_ref"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` -} - func (s Store) AgentsPath() string { return filepath.Join(s.Root(), "agents") } @@ -65,7 +54,7 @@ func (s Store) ReadAgentBinding(agentID string) (*AgentBinding, error) { if err != nil { return nil, fmt.Errorf("cannot read Agent binding for %s: %w", agentID, err) } - var stored storedBinding + var stored AgentBinding if err := json.Unmarshal(data, &stored); err != nil { return nil, fmt.Errorf("Agent binding for %s is corrupt", agentID) } @@ -75,22 +64,16 @@ func (s Store) ReadAgentBinding(agentID string) (*AgentBinding, error) { if stored.AgentID != agentID { return nil, fmt.Errorf("Agent binding for %s is corrupt", agentID) } - return &AgentBinding{ - SchemaVersion: stored.SchemaVersion, - AgentID: stored.AgentID, - Provider: stored.Provider, - BaseURL: stored.BaseURL, - Model: stored.Model, - ProfileRef: stored.ProfileRef, - CreatedAt: stored.CreatedAt, - UpdatedAt: stored.UpdatedAt, - }, nil + return &stored, nil } -func (s Store) ListAgentBindings() map[string]AgentBinding { +func (s Store) ListAgentBindings() (map[string]AgentBinding, error) { entries, err := os.ReadDir(s.AgentsPath()) + if os.IsNotExist(err) { + return map[string]AgentBinding{}, nil + } if err != nil { - return map[string]AgentBinding{} + return nil, fmt.Errorf("cannot list Agent bindings: %w", err) } result := make(map[string]AgentBinding) ids := make([]string, 0, len(entries)) @@ -107,11 +90,14 @@ func (s Store) ListAgentBindings() map[string]AgentBinding { sort.Strings(ids) for _, id := range ids { binding, err := s.ReadAgentBinding(id) - if err == nil && binding != nil { + if err != nil { + return nil, err + } + if binding != nil { result[id] = *binding } } - return result + return result, nil } func (s Store) WriteAgentBinding(ctx context.Context, agentID string, request BindingWriteRequest) (AgentBinding, error) { @@ -125,7 +111,10 @@ func (s Store) WriteAgentBinding(ctx context.Context, agentID string, request Bi if strings.TrimSpace(request.Provider) == "" || strings.TrimSpace(request.BaseURL) == "" || strings.TrimSpace(request.Model) == "" { return AgentBinding{}, oneerrors.New(oneerrors.InvalidRequest, "Agent binding requires provider, base URL, and model") } - existing, _ := s.ReadAgentBinding(agentID) + existing, err := s.ReadAgentBinding(agentID) + if err != nil { + return AgentBinding{}, err + } now := s.clock().UTC().Format(time.RFC3339) profileRef := request.ProfileRef created := now @@ -137,7 +126,7 @@ func (s Store) WriteAgentBinding(ctx context.Context, agentID string, request Bi created = existing.CreatedAt } } - stored := storedBinding{ + stored := AgentBinding{ SchemaVersion: 1, AgentID: agentID, Provider: request.Provider, @@ -155,5 +144,5 @@ func (s Store) WriteAgentBinding(ctx context.Context, agentID string, request Bi if _, err := s.filesystem().AtomicWrite(ctx, path, data, false); err != nil { return AgentBinding{}, err } - return AgentBinding(stored), nil + return stored, nil } diff --git a/internal/profile/binding_test.go b/internal/profile/binding_test.go index c2333e2..dec1034 100644 --- a/internal/profile/binding_test.go +++ b/internal/profile/binding_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "os" "path/filepath" - "reflect" "strings" "testing" ) @@ -46,7 +45,7 @@ func TestAgentBindingWriteReadAndPreserveProfileReference(t *testing.T) { assertProfileMode(t, func() string { path, _ := store.AgentBindingPath("codex"); return path }(), 0o600) } -func TestAgentBindingListSkipsCorruptAndInvalidFiles(t *testing.T) { +func TestAgentBindingListRejectsCorruptFiles(t *testing.T) { store := testStore(t, t.TempDir(), "linux") if err := os.MkdirAll(store.AgentsPath(), 0o700); err != nil { t.Fatal(err) @@ -65,15 +64,31 @@ func TestAgentBindingListSkipsCorruptAndInvalidFiles(t *testing.T) { if _, err := store.WriteAgentBinding(context.Background(), "opencode", BindingWriteRequest{Provider: "ppio", BaseURL: "https://api.ppio.com/openai", Model: "m"}); err != nil { t.Fatal(err) } - bindings := store.ListAgentBindings() - if len(bindings) != 1 || bindings["opencode"].Model != "m" { - t.Fatalf("bindings = %#v", bindings) + if _, err := store.ListAgentBindings(); err == nil { + t.Fatal("corrupt Agent binding was ignored") } if _, err := store.ReadAgentBinding("../escape"); err == nil { t.Fatal("traversal Agent ID unexpectedly accepted") } } +func TestAgentBindingWriteDoesNotOverwriteCorruptFile(t *testing.T) { + store := testStore(t, t.TempDir(), "linux") + path, _ := store.AgentBindingPath("codex") + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte("{"), 0o600); err != nil { + t.Fatal(err) + } + if _, err := store.WriteAgentBinding(context.Background(), "codex", BindingWriteRequest{Provider: "p", BaseURL: "x", Model: "m"}); err == nil { + t.Fatal("write overwrote corrupt Agent binding") + } + if data, err := os.ReadFile(path); err != nil || string(data) != "{" { + t.Fatalf("binding changed to %q, err=%v", data, err) + } +} + func TestAgentBindingRejectsInvalidInputAndHonorsCancellation(t *testing.T) { store := testStore(t, t.TempDir(), "linux") for _, request := range []BindingWriteRequest{{Provider: "", BaseURL: "x", Model: "m"}, {Provider: "p", BaseURL: "", Model: "m"}, {Provider: "p", BaseURL: "x", Model: ""}} { @@ -86,7 +101,7 @@ func TestAgentBindingRejectsInvalidInputAndHonorsCancellation(t *testing.T) { if _, err := store.WriteAgentBinding(ctx, "codex", BindingWriteRequest{Provider: "p", BaseURL: "x", Model: "m"}); err == nil { t.Fatal("cancelled binding write succeeded") } - if got := store.ListAgentBindings(); !reflect.DeepEqual(got, map[string]AgentBinding{}) { - t.Fatalf("cancelled write changed bindings = %#v", got) + if got, err := store.ListAgentBindings(); err != nil || len(got) != 0 { + t.Fatalf("cancelled write changed bindings = %#v, %v", got, err) } } diff --git a/internal/profile/store.go b/internal/profile/store.go index 0fa2582..ba7e170 100644 --- a/internal/profile/store.go +++ b/internal/profile/store.go @@ -83,13 +83,6 @@ func NewStore(home, osID string) Store { return Store{Home: home, OS: osID, FS: &filesystem, Now: time.Now} } -func NewStoreWithDependencies(home, osID string, filesystem securefs.Store, now func() time.Time) Store { - if now == nil { - now = time.Now - } - return Store{Home: home, OS: osID, FS: &filesystem, Now: now} -} - func ValidateID(id string) error { if !profileIDPattern.MatchString(id) { return oneerrors.New(oneerrors.InvalidRequest, "Profile ID must start with a lowercase letter or digit and use only lowercase letters, digits, '-' or '_'") @@ -127,10 +120,13 @@ func (s Store) SecretPath(id string) (string, error) { return filepath.Join(s.Root(), "secrets", id+"."+suffix), nil } -func (s Store) List() []Profile { +func (s Store) List() ([]Profile, error) { entries, err := os.ReadDir(s.ProfilesPath()) + if os.IsNotExist(err) { + return []Profile{}, nil + } if err != nil { - return []Profile{} + return nil, fmt.Errorf("cannot list Profiles: %w", err) } profiles := make([]Profile, 0, len(entries)) for _, entry := range entries { @@ -140,20 +136,27 @@ func (s Store) List() []Profile { path := filepath.Join(s.ProfilesPath(), entry.Name()) data, err := os.ReadFile(path) if err != nil { - continue + return nil, fmt.Errorf("cannot read Profile %s: %w", strings.TrimSuffix(entry.Name(), ".json"), err) } profile, err := decodeStored(data) - if err != nil || ValidateID(profile.ID) != nil { - continue + if err != nil { + return nil, fmt.Errorf("Profile %s is corrupt: %w", strings.TrimSuffix(entry.Name(), ".json"), err) + } + if err := ValidateID(profile.ID); err != nil { + return nil, fmt.Errorf("Profile %s is corrupt: %w", strings.TrimSuffix(entry.Name(), ".json"), err) } secret, err := s.SecretPath(profile.ID) - if err == nil { - _, statErr := os.Stat(secret) - profile.HasKey = statErr == nil + if err != nil { + return nil, err + } + _, statErr := os.Stat(secret) + if statErr != nil && !os.IsNotExist(statErr) { + return nil, fmt.Errorf("cannot inspect Profile secret %s: %w", profile.ID, statErr) } + profile.HasKey = statErr == nil profiles = append(profiles, profile) } - return profiles + return profiles, nil } func (s Store) LoadActive() ActiveResult { diff --git a/internal/profile/store_test.go b/internal/profile/store_test.go index bb11c32..a496033 100644 --- a/internal/profile/store_test.go +++ b/internal/profile/store_test.go @@ -40,8 +40,8 @@ func TestValidateIDAndPathsRejectTraversal(t *testing.T) { func TestEmptyStoreAndStableListProjection(t *testing.T) { store := NewStore(t.TempDir(), "linux") - if got := store.List(); len(got) != 0 { - t.Fatalf("empty List() = %#v", got) + if got, err := store.List(); err != nil || len(got) != 0 { + t.Fatalf("empty List() = %#v, %v", got, err) } if result := store.LoadActive(); result.Profile != nil || result.ID != "" || result.Error != "" { t.Fatalf("empty LoadActive() = %#v", result) @@ -58,7 +58,10 @@ func TestEmptyStoreAndStableListProjection(t *testing.T) { if err := os.WriteFile(secret, []byte("export ONEAGENT_API_KEY=hidden\n"), 0o600); err != nil { t.Fatal(err) } - profiles := store.List() + profiles, err := store.List() + if err != nil { + t.Fatal(err) + } if len(profiles) != 2 || profiles[0].ID != "a-profile" || profiles[1].ID != "b-profile" || !profiles[0].HasKey || profiles[1].HasKey { t.Fatalf("stable profile list = %#v", profiles) } @@ -89,7 +92,7 @@ func TestLoadActiveV2PreservesPointerAndStripsSecrets(t *testing.T) { } } -func TestListSkipsCorruptAndSecretContentsNeverEnterSummary(t *testing.T) { +func TestListRejectsCorruptProfile(t *testing.T) { store := NewStore(t.TempDir(), "linux") if err := os.MkdirAll(store.ProfilesPath(), 0o700); err != nil { t.Fatal(err) @@ -100,9 +103,7 @@ func TestListSkipsCorruptAndSecretContentsNeverEnterSummary(t *testing.T) { if err := os.WriteFile(filepath.Join(store.ProfilesPath(), "noid.json"), []byte(`{"schema_version":2,"provider":"ppio"}`), 0o600); err != nil { t.Fatal(err) } - writeProfileFixture(t, store, "real", `{"schema_version":2,"id":"real","provider":"ppio","model":"m"}`) - profiles := store.List() - if len(profiles) != 1 || profiles[0].ID != "real" { - t.Fatalf("filtered profiles = %#v", profiles) + if _, err := store.List(); err == nil { + t.Fatal("corrupt Profile was ignored") } } diff --git a/internal/profile/write_test.go b/internal/profile/write_test.go index 25bbd91..fa2c636 100644 --- a/internal/profile/write_test.go +++ b/internal/profile/write_test.go @@ -15,7 +15,7 @@ import ( func testStore(t *testing.T, home, osID string) Store { t.Helper() filesystem := securefs.New(securefs.Options{OS: osID, Now: fixedProfileClock}) - return NewStoreWithDependencies(home, osID, filesystem, fixedProfileClock) + return Store{Home: home, OS: osID, FS: &filesystem, Now: fixedProfileClock} } func fixedProfileClock() time.Time { @@ -53,8 +53,8 @@ func TestSaveProfileIsolatesSecretAndPreservesHistory(t *testing.T) { if err != nil || !strings.Contains(string(secretData), "ONEAGENT_API_KEY") { t.Fatalf("secret file = %q, err=%v", secretData, err) } - if got := store.List(); len(got) != 1 || !got[0].HasKey { - t.Fatalf("List() = %#v", got) + if got, err := store.List(); err != nil || len(got) != 1 || !got[0].HasKey { + t.Fatalf("List() = %#v, %v", got, err) } created := profile.CreatedAt updated, err := store.Save(context.Background(), SaveRequest{ @@ -187,7 +187,7 @@ func TestWindowsSecretUsesPowerShellQuoting(t *testing.T) { Run: func(context.Context, []string) error { return nil }, Now: fixedProfileClock, }) - store = NewStoreWithDependencies(store.Home, "windows", filesystem, fixedProfileClock) + store = Store{Home: store.Home, OS: "windows", FS: &filesystem, Now: fixedProfileClock} if _, err := store.Save(context.Background(), SaveRequest{ID: "win", Provider: "ppio", Model: "m", APIKey: "key'value"}); err != nil { t.Fatal(err) } diff --git a/internal/provider/client.go b/internal/provider/client.go index 1d47157..9d76de1 100644 --- a/internal/provider/client.go +++ b/internal/provider/client.go @@ -40,19 +40,6 @@ func NewClient(doer HTTPDoer) *Client { return &Client{doer: doer, timeout: defaultTimeout, maxBody: defaultMaxBody} } -// NewClientWithLimits is useful for integration tests and keeps operational -// limits explicit at the one place where requests are created. -func NewClientWithLimits(doer HTTPDoer, timeout time.Duration, maxBody int64) *Client { - client := NewClient(doer) - if timeout > 0 { - client.timeout = timeout - } - if maxBody > 0 { - client.maxBody = maxBody - } - return client -} - type ProbeResult struct { OK bool `json:"ok"` Reachable bool `json:"reachable"` diff --git a/internal/provider/client_test.go b/internal/provider/client_test.go index 0cd11ce..77b5ab9 100644 --- a/internal/provider/client_test.go +++ b/internal/provider/client_test.go @@ -177,9 +177,9 @@ func TestListModelsTransportTimeoutAndSizeLimit(t *testing.T) { if err != nil || result.ErrorCode == nil || *result.ErrorCode != oneerrors.Timeout { t.Fatalf("timeout result = %#v, err=%v", result, err) } - large := NewClientWithLimits(fakeDoer(func(*http.Request) (*http.Response, error) { + large := &Client{doer: fakeDoer(func(*http.Request) (*http.Response, error) { return fakeResponse(http.StatusOK, strings.Repeat("x", 32)), nil - }), time.Second, 8) + }), timeout: time.Second, maxBody: 8} result, err = large.ListModels(context.Background(), "ppio", "key", "") if err != nil || result.ErrorCode == nil || *result.ErrorCode != oneerrors.ModelsUnsupported { t.Fatalf("large response result = %#v, err=%v", result, err) diff --git a/scripts/generate_third_party_licenses.py b/scripts/generate_third_party_licenses.py index 57149ce..8c7d8df 100755 --- a/scripts/generate_third_party_licenses.py +++ b/scripts/generate_third_party_licenses.py @@ -192,7 +192,7 @@ def npm_license_source(name: str, package_path: Path) -> Path: if find_license_files(package_path): return package_path if name == "@wailsio/runtime": - return module_directory("github.com/wailsapp/wails/v3", "v3.0.0-beta.3") + return module_directory("github.com/wailsapp/wails/v3", "v3.0.0-beta.4") raise RuntimeError(f"npm package {name} has no bundled license file in {package_path}") diff --git a/third_party/THIRD_PARTY_NOTICES.md b/third_party/THIRD_PARTY_NOTICES.md index f8b6c00..0c71275 100644 --- a/third_party/THIRD_PARTY_NOTICES.md +++ b/third_party/THIRD_PARTY_NOTICES.md @@ -17,7 +17,7 @@ the embedded frontend. Full license and notice texts are included under | go | `github.com/adrg/xdg` | `v0.5.3` | macos-arm64, macos-x64, windows-arm64, windows-x64 | MIT | `licenses/go/github.com_adrg_xdg@v0.5.3/LICENSE` | | go | `github.com/go-ole/go-ole` | `v1.3.0` | windows-arm64, windows-x64 | MIT | `licenses/go/github.com_go-ole_go-ole@v1.3.0/LICENSE` | | go | `github.com/pelletier/go-toml/v2` | `v2.4.3` | macos-arm64, macos-x64, windows-arm64, windows-x64 | MIT | `licenses/go/github.com_pelletier_go-toml_v2@v2.4.3/LICENSE` | -| go | `github.com/wailsapp/wails/v3` | `v3.0.0-beta.3` | macos-arm64, macos-x64, windows-arm64, windows-x64 | MIT | `licenses/go/github.com_wailsapp_wails_v3@v3.0.0-beta.3/LICENSE` | +| go | `github.com/wailsapp/wails/v3` | `v3.0.0-beta.4` | macos-arm64, macos-x64, windows-arm64, windows-x64 | MIT | `licenses/go/github.com_wailsapp_wails_v3@v3.0.0-beta.4/LICENSE` | | go | `golang.org/x/mod` | `v0.35.0` | macos-arm64, macos-x64, windows-arm64, windows-x64 | BSD-3-Clause | `licenses/go/golang.org_x_mod@v0.35.0/LICENSE` | | go | `golang.org/x/sys` | `v0.47.0` | windows-arm64, windows-x64 | BSD-3-Clause | `licenses/go/golang.org_x_sys@v0.47.0/LICENSE` | | npm | `@wailsio/runtime` | `3.0.0-alpha2.117` | frontend | MIT | `licenses/npm/wailsio_runtime@3.0.0-alpha2.117/LICENSE` | diff --git a/third_party/manifest.json b/third_party/manifest.json index 6476896..ebd51e3 100644 --- a/third_party/manifest.json +++ b/third_party/manifest.json @@ -115,7 +115,7 @@ "ecosystem": "go", "license": "MIT", "license_files": [ - "licenses/go/github.com_wailsapp_wails_v3@v3.0.0-beta.3/LICENSE" + "licenses/go/github.com_wailsapp_wails_v3@v3.0.0-beta.4/LICENSE" ], "modification": "", "name": "github.com/wailsapp/wails/v3", @@ -125,7 +125,7 @@ "windows-arm64", "windows-x64" ], - "version": "v3.0.0-beta.3" + "version": "v3.0.0-beta.4" }, { "ecosystem": "go", From 5059a72de0b9ac2d048c2b2238585348dda8e405 Mon Sep 17 00:00:00 2001 From: Paul Liu <20290410+Paulkm2006@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:12:12 +0800 Subject: [PATCH 2/6] chore: update frontend dependencies --- frontend/package.json | 20 +- frontend/pnpm-lock.yaml | 490 +++++++++++++++++----------------------- 2 files changed, 222 insertions(+), 288 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 5c8099c..e57193d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,24 +16,24 @@ }, "dependencies": { "@wailsio/runtime": "3.0.0-beta.1", - "lucide-react": "1.25.0", + "lucide-react": "1.28.0", "react": "19.2.8", "react-dom": "19.2.8", - "react-router-dom": "7.18.1" + "react-router-dom": "7.18.2" }, "devDependencies": { - "@playwright/test": "1.61.1", + "@playwright/test": "1.62.1", "@testing-library/jest-dom": "7.0.0", "@testing-library/react": "16.3.2", - "@testing-library/user-event": "14.6.1", - "@types/node": "26.1.1", - "@types/react": "19.2.17", - "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "6.0.3", + "@testing-library/user-event": "14.6.3", + "@types/node": "26.1.2", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.4", + "@vitejs/plugin-react": "6.0.5", "@vitest/coverage-v8": "4.1.10", - "jsdom": "29.1.1", + "jsdom": "30.0.1", "typescript": "7.0.2", - "vite": "8.1.5", + "vite": "8.2.0", "vitest": "4.1.10" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 904ce2d..36217b0 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: 3.0.0-beta.1 version: 3.0.0-beta.1 lucide-react: - specifier: 1.25.0 - version: 1.25.0(react@19.2.8) + specifier: 1.28.0 + version: 1.28.0(react@19.2.8) react: specifier: 19.2.8 version: 19.2.8 @@ -21,68 +21,61 @@ importers: specifier: 19.2.8 version: 19.2.8(react@19.2.8) react-router-dom: - specifier: 7.18.1 - version: 7.18.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + specifier: 7.18.2 + version: 7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) devDependencies: '@playwright/test': - specifier: 1.61.1 - version: 1.61.1 + specifier: 1.62.1 + version: 1.62.1 '@testing-library/jest-dom': specifier: 7.0.0 version: 7.0.0(@testing-library/dom@10.4.1) '@testing-library/react': specifier: 16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@testing-library/user-event': - specifier: 14.6.1 - version: 14.6.1(@testing-library/dom@10.4.1) + specifier: 14.6.3 + version: 14.6.3(@testing-library/dom@10.4.1) '@types/node': - specifier: 26.1.1 - version: 26.1.1 + specifier: 26.1.2 + version: 26.1.2 '@types/react': - specifier: 19.2.17 - version: 19.2.17 + specifier: 19.2.18 + version: 19.2.18 '@types/react-dom': - specifier: 19.2.3 - version: 19.2.3(@types/react@19.2.17) + specifier: 19.2.4 + version: 19.2.4(@types/react@19.2.18) '@vitejs/plugin-react': - specifier: 6.0.3 - version: 6.0.3(vite@8.1.5(@types/node@26.1.1)) + specifier: 6.0.5 + version: 6.0.5(vite@8.2.0(@types/node@26.1.2)) '@vitest/coverage-v8': specifier: 4.1.10 version: 4.1.10(vitest@4.1.10) jsdom: - specifier: 29.1.1 - version: 29.1.1 + specifier: 30.0.1 + version: 30.0.1 typescript: specifier: 7.0.2 version: 7.0.2 vite: - specifier: 8.1.5 - version: 8.1.5(@types/node@26.1.1) + specifier: 8.2.0 + version: 8.2.0(@types/node@26.1.2) vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1)(vite@8.1.5(@types/node@26.1.1)) + version: 4.1.10(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.2.0(@types/node@26.1.2)) packages: '@adobe/css-tools@4.5.0': resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} - '@asamuzakjp/css-color@5.1.11': - resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/dom-selector@7.1.1': - resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@asamuzakjp/css-color@6.0.5': + resolution: {integrity: sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==} + engines: {node: ^22.13.0 || >=24.0.0} - '@asamuzakjp/generational-cache@1.0.1': - resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/nwsapi@2.3.9': - resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} @@ -96,8 +89,8 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true @@ -105,8 +98,8 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@1.0.2': @@ -153,15 +146,6 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - '@exodus/bytes@1.15.1': resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -188,112 +172,100 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@napi-rs/wasm-runtime@1.2.0': - resolution: {integrity: sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - peerDependencies: - '@emnapi/core': ^2.0.0-alpha.3 - '@emnapi/runtime': ^2.0.0-alpha.3 - - '@oxc-project/types@0.139.0': - resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@oxc-project/types@0.142.0': + resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} - '@playwright/test@1.61.1': - resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} - engines: {node: '>=18'} + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} hasBin: true - '@rolldown/binding-android-arm64@1.1.5': - resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + '@rolldown/binding-android-arm64@1.2.2': + resolution: {integrity: sha512-l7x215OGvo1s52JWmR8U/DAVzEDWBCIbTm28aeJV/WDTSHgcKXaZTuBT0hJMs5NggilfJTW3clZVvd24yfKJxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.1.5': - resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + '@rolldown/binding-darwin-arm64@1.2.2': + resolution: {integrity: sha512-9u9Xv6c1AJZT0FfwH5vrMG5Jjcwhc1MlyrPu0XfTqkzsmqfks2M6W/o5XwAJgVVN/jHpqqngC1WevHKKTIUtIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.5': - resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + '@rolldown/binding-darwin-x64@1.2.2': + resolution: {integrity: sha512-9W1mbGZAfW3oqd85bhBkmpyHCCzL1TeG/zFFP3vg7b0rlly8cxOcre5nXwz+LHazCwac2MNWgPdPCHndABjpWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.1.5': - resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + '@rolldown/binding-freebsd-x64@1.2.2': + resolution: {integrity: sha512-0p1lhiCSCyaerFwtrdZQUx7NqGk6LQnaRKWX7tFQqwQgvX0rjM15cIkm3pax1UpEakK14C4mOxx/jSqCBdBRqQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.2': + resolution: {integrity: sha512-e+cOJXrJ2L3zx6YzqPg+f6Wbk3V1cKB8bOhbaYdVYN3DdquzNdRAmrbETz1qnt5yp/c7JNlNjmITiA2cVneQ7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + '@rolldown/binding-linux-arm64-gnu@1.2.2': + resolution: {integrity: sha512-JsSMsj6sNat/MuhG5fnBD7QgbtpHKVe30x5/bAVirDHdhoQRXJkF6xc0Jqk8O4fiCUQAzMOoH9wZi3m60c8wtg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + '@rolldown/binding-linux-arm64-musl@1.2.2': + resolution: {integrity: sha512-B5G/zJdHaoJn9vD50eGHWkiWfmq8Uhi3IiLPJTzmZTrAalk1bztUikSXo0qga18ibE0IXboyeMUnhPjhAJ45wQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + '@rolldown/binding-linux-ppc64-gnu@1.2.2': + resolution: {integrity: sha512-6mC/awzKka8W6EoekjegpfGkjz8jXWDX63pqu/HYVpyKtZfu65Jsh4QAH3Kej3CAv/c1oGX7psTmFEbr0mDxLA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + '@rolldown/binding-linux-s390x-gnu@1.2.2': + resolution: {integrity: sha512-412MX9fJLdA1IK28EZnc8jYv2HRTleOZgfLQumJ5zy7OeJLZlg/CETwFaXjNmGVxG51cFHpKLqb5LKvBC+HsHA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + '@rolldown/binding-linux-x64-gnu@1.2.2': + resolution: {integrity: sha512-Q/+HI/ToJafZ1iCqGgVQXUEkIjufHCTF0gBQ2a5o3cg7GJ2h0qyq3nvvSmU+bGda2/7ygXpTY4TM6gO9OhQ0ZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + '@rolldown/binding-linux-x64-musl@1.2.2': + resolution: {integrity: sha512-ZKp/w41n6wCvxzxQHtQSbuphfX3Y4cCvbjkKHusrLx4lh+JWLTU7StSltO/DKARISzbj368d+qUaCjI8K2wzXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + '@rolldown/binding-openharmony-arm64@1.2.2': + resolution: {integrity: sha512-pxE6xD4KS3eAROkKK5yrhB9/3+vhlhVGMvlQLbdpzrBGDbKrnzx3RLwPaHvasLo6jgaiBLn7e04Df9C4tYhjmA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + '@rolldown/binding-win32-arm64-msvc@1.2.2': + resolution: {integrity: sha512-4MqEue5re+xIZzAWsB8sj0P1kqZySWqIuN4t6QaIO/YA6SFwySOLruvWQFzfmqk8LBK2P30KCSJwf6mJCZZ5/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + '@rolldown/binding-win32-x64-msvc@1.2.2': + resolution: {integrity: sha512-NweNxxD0Nf9t8v7kodun45Ijp3EIwYY+uydPP6qBEYvfBqhIjN6dZMzlQja3tqX/aLs3F3Uz+AxDpKgRhpOZQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -329,15 +301,12 @@ packages: '@types/react-dom': optional: true - '@testing-library/user-event@14.6.1': - resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + '@testing-library/user-event@14.6.3': + resolution: {integrity: sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -359,16 +328,16 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - '@types/node@26.1.1': - resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} + '@types/node@26.1.2': + resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + '@types/react-dom@19.2.4': + resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -496,8 +465,8 @@ packages: cpu: [x64] os: [win32] - '@vitejs/plugin-react@6.0.3': - resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} + '@vitejs/plugin-react@6.0.5': + resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 @@ -701,11 +670,11 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - jsdom@29.1.1: - resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} peerDependencies: - canvas: ^3.0.0 + canvas: ^3.2.3 peerDependenciesMeta: canvas: optional: true @@ -788,8 +757,8 @@ packages: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} - lucide-react@1.25.0: - resolution: {integrity: sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw==} + lucide-react@1.28.0: + resolution: {integrity: sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -800,8 +769,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.5.3: - resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + magicast@0.5.4: + resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -814,8 +783,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.17: + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -836,18 +805,18 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} - playwright-core@1.61.1: - resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} - engines: {node: '>=18'} + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} hasBin: true - playwright@1.61.1: - resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} - engines: {node: '>=18'} + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} hasBin: true - postcss@8.5.24: - resolution: {integrity: sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw==} + postcss@8.5.25: + resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} engines: {node: ^10 || ^12 || >=14} pretty-format@27.0.2: @@ -866,15 +835,15 @@ packages: react-is@17.0.1: resolution: {integrity: sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==} - react-router-dom@7.18.1: - resolution: {integrity: sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==} + react-router-dom@7.18.2: + resolution: {integrity: sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@7.18.1: - resolution: {integrity: sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==} + react-router@7.18.2: + resolution: {integrity: sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -895,8 +864,8 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - rolldown@1.1.5: - resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + rolldown@1.2.2: + resolution: {integrity: sha512-opwpo1tQBAcpSUJDt94B7hhLNGOKjCdE//XXjeLrnx9b83bjnw45tXdg1b09yEw/VLFBJGZpwRULMmOZo7ol+A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -942,8 +911,8 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -954,11 +923,11 @@ packages: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} - tldts-core@7.4.9: - resolution: {integrity: sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==} + tldts-core@7.4.10: + resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} - tldts@7.4.9: - resolution: {integrity: sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==} + tldts@7.4.10: + resolution: {integrity: sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==} hasBin: true tough-cookie@6.0.2: @@ -969,9 +938,6 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - typescript@7.0.2: resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} engines: {node: '>=16.20.0'} @@ -980,17 +946,17 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} - engines: {node: '>=20.18.1'} + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} + engines: {node: '>=22.19.0'} - vite@8.1.5: - resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + vite@8.2.0: + resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 + '@vitejs/devtools': ^0.4.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -1084,6 +1050,10 @@ packages: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -1100,25 +1070,20 @@ snapshots: '@adobe/css-tools@4.5.0': {} - '@asamuzakjp/css-color@5.1.11': + '@asamuzakjp/css-color@6.0.5': dependencies: - '@asamuzakjp/generational-cache': 1.0.1 '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 - '@asamuzakjp/dom-selector@7.1.1': + '@asamuzakjp/dom-selector@8.3.2': dependencies: - '@asamuzakjp/generational-cache': 1.0.1 - '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - - '@asamuzakjp/generational-cache@1.0.1': {} - - '@asamuzakjp/nwsapi@2.3.9': {} + lru-cache: 11.5.2 '@babel/code-frame@7.29.7': dependencies: @@ -1130,13 +1095,13 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} - '@babel/parser@7.29.7': + '@babel/parser@7.29.8': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/runtime@7.29.7': {} - '@babel/types@7.29.7': + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 @@ -1171,29 +1136,13 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@emnapi/core@1.11.1': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.11.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.8.1 - optional: true - '@exodus/bytes@1.15.1': {} '@jest/types@27.0.2': dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 26.1.1 + '@types/node': 26.1.2 '@types/yargs': 16.0.11 chalk: 4.0.0 @@ -1208,66 +1157,52 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.14 - '@napi-rs/wasm-runtime@1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@oxc-project/types@0.139.0': {} + '@oxc-project/types@0.142.0': {} - '@playwright/test@1.61.1': + '@playwright/test@1.62.1': dependencies: - playwright: 1.61.1 + playwright: 1.62.1 - '@rolldown/binding-android-arm64@1.1.5': + '@rolldown/binding-android-arm64@1.2.2': optional: true - '@rolldown/binding-darwin-arm64@1.1.5': + '@rolldown/binding-darwin-arm64@1.2.2': optional: true - '@rolldown/binding-darwin-x64@1.1.5': + '@rolldown/binding-darwin-x64@1.2.2': optional: true - '@rolldown/binding-freebsd-x64@1.1.5': + '@rolldown/binding-freebsd-x64@1.2.2': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + '@rolldown/binding-linux-arm-gnueabihf@1.2.2': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.5': + '@rolldown/binding-linux-arm64-gnu@1.2.2': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.5': + '@rolldown/binding-linux-arm64-musl@1.2.2': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.5': + '@rolldown/binding-linux-ppc64-gnu@1.2.2': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.5': + '@rolldown/binding-linux-s390x-gnu@1.2.2': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.5': + '@rolldown/binding-linux-x64-gnu@1.2.2': optional: true - '@rolldown/binding-linux-x64-musl@1.1.5': + '@rolldown/binding-linux-x64-musl@1.2.2': optional: true - '@rolldown/binding-openharmony-arm64@1.1.5': - optional: true - - '@rolldown/binding-wasm32-wasi@1.1.5': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@rolldown/binding-openharmony-arm64@1.2.2': optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.5': + '@rolldown/binding-win32-arm64-msvc@1.2.2': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.5': + '@rolldown/binding-win32-x64-msvc@1.2.2': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -1295,25 +1230,20 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@babel/runtime': 7.29.7 '@testing-library/dom': 10.4.1 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + '@testing-library/user-event@14.6.3(@testing-library/dom@10.4.1)': dependencies: '@testing-library/dom': 10.4.1 - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.8.1 - optional: true - '@types/aria-query@5.0.4': {} '@types/chai@5.2.3': @@ -1335,15 +1265,15 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 - '@types/node@26.1.1': + '@types/node@26.1.2': dependencies: undici-types: 8.3.0 - '@types/react-dom@19.2.3(@types/react@19.2.17)': + '@types/react-dom@19.2.4(@types/react@19.2.18)': dependencies: - '@types/react': 19.2.17 + '@types/react': 19.2.18 - '@types/react@19.2.17': + '@types/react@19.2.18': dependencies: csstype: 3.2.3 @@ -1413,10 +1343,10 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true - '@vitejs/plugin-react@6.0.3(vite@8.1.5(@types/node@26.1.1))': + '@vitejs/plugin-react@6.0.5(vite@8.2.0(@types/node@26.1.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.1.5(@types/node@26.1.1) + vite: 8.2.0(@types/node@26.1.2) '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': dependencies: @@ -1426,11 +1356,11 @@ snapshots: istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 - magicast: 0.5.3 + magicast: 0.5.4 obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1)(vite@8.1.5(@types/node@26.1.1)) + vitest: 4.1.10(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.2.0(@types/node@26.1.2)) '@vitest/expect@4.1.10': dependencies: @@ -1441,13 +1371,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.1.1))': + '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@26.1.2))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.5(@types/node@26.1.1) + vite: 8.2.0(@types/node@26.1.2) '@vitest/pretty-format@4.1.10': dependencies: @@ -1595,10 +1525,10 @@ snapshots: js-tokens@4.0.0: {} - jsdom@29.1.1: + jsdom@30.0.1: dependencies: - '@asamuzakjp/css-color': 5.1.11 - '@asamuzakjp/dom-selector': 7.1.1 + '@asamuzakjp/css-color': 6.0.5 + '@asamuzakjp/dom-selector': 8.3.2 '@bramus/specificity': 2.4.2 '@csstools/css-syntax-patches-for-csstree': 1.1.7(css-tree@3.2.1) '@exodus/bytes': 1.15.1 @@ -1612,11 +1542,11 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.2 - undici: 7.29.0 + undici: 8.10.0 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 + whatwg-url: 17.1.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -1672,7 +1602,7 @@ snapshots: lru-cache@11.5.2: {} - lucide-react@1.25.0(react@19.2.8): + lucide-react@1.28.0(react@19.2.8): dependencies: react: 19.2.8 @@ -1682,10 +1612,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.5.3: + magicast@0.5.4: dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 source-map-js: 1.2.1 make-dir@4.0.0: @@ -1696,7 +1626,7 @@ snapshots: min-indent@1.0.1: {} - nanoid@3.3.16: {} + nanoid@3.3.17: {} obug@2.1.4: {} @@ -1710,17 +1640,17 @@ snapshots: picomatch@4.0.5: {} - playwright-core@1.61.1: {} + playwright-core@1.62.1: {} - playwright@1.61.1: + playwright@1.62.1: dependencies: - playwright-core: 1.61.1 + playwright-core: 1.62.1 optionalDependencies: fsevents: 2.3.2 - postcss@8.5.24: + postcss@8.5.25: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -1740,13 +1670,13 @@ snapshots: react-is@17.0.1: {} - react-router-dom@7.18.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + react-router-dom@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - react-router: 7.18.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + react-router: 7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react-router@7.18.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + react-router@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: cookie: 1.0.1 react: 19.2.8 @@ -1763,26 +1693,25 @@ snapshots: require-from-string@2.0.2: {} - rolldown@1.1.5: + rolldown@1.2.2: dependencies: - '@oxc-project/types': 0.139.0 + '@oxc-project/types': 0.142.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.5 - '@rolldown/binding-darwin-arm64': 1.1.5 - '@rolldown/binding-darwin-x64': 1.1.5 - '@rolldown/binding-freebsd-x64': 1.1.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 - '@rolldown/binding-linux-arm64-gnu': 1.1.5 - '@rolldown/binding-linux-arm64-musl': 1.1.5 - '@rolldown/binding-linux-ppc64-gnu': 1.1.5 - '@rolldown/binding-linux-s390x-gnu': 1.1.5 - '@rolldown/binding-linux-x64-gnu': 1.1.5 - '@rolldown/binding-linux-x64-musl': 1.1.5 - '@rolldown/binding-openharmony-arm64': 1.1.5 - '@rolldown/binding-wasm32-wasi': 1.1.5 - '@rolldown/binding-win32-arm64-msvc': 1.1.5 - '@rolldown/binding-win32-x64-msvc': 1.1.5 + '@rolldown/binding-android-arm64': 1.2.2 + '@rolldown/binding-darwin-arm64': 1.2.2 + '@rolldown/binding-darwin-x64': 1.2.2 + '@rolldown/binding-freebsd-x64': 1.2.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.2 + '@rolldown/binding-linux-arm64-gnu': 1.2.2 + '@rolldown/binding-linux-arm64-musl': 1.2.2 + '@rolldown/binding-linux-ppc64-gnu': 1.2.2 + '@rolldown/binding-linux-s390x-gnu': 1.2.2 + '@rolldown/binding-linux-x64-gnu': 1.2.2 + '@rolldown/binding-linux-x64-musl': 1.2.2 + '@rolldown/binding-openharmony-arm64': 1.2.2 + '@rolldown/binding-win32-arm64-msvc': 1.2.2 + '@rolldown/binding-win32-x64-msvc': 1.2.2 saxes@6.0.0: dependencies: @@ -1814,7 +1743,7 @@ snapshots: tinybench@2.9.0: {} - tinyexec@1.2.4: {} + tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: @@ -1823,23 +1752,20 @@ snapshots: tinyrainbow@3.1.1: {} - tldts-core@7.4.9: {} + tldts-core@7.4.10: {} - tldts@7.4.9: + tldts@7.4.10: dependencies: - tldts-core: 7.4.9 + tldts-core: 7.4.10 tough-cookie@6.0.2: dependencies: - tldts: 7.4.9 + tldts: 7.4.10 tr46@6.0.0: dependencies: punycode: 2.3.1 - tslib@2.8.1: - optional: true - typescript@7.0.2: optionalDependencies: '@typescript/typescript-aix-ppc64': 7.0.2 @@ -1865,23 +1791,23 @@ snapshots: undici-types@8.3.0: {} - undici@7.29.0: {} + undici@8.10.0: {} - vite@8.1.5(@types/node@26.1.1): + vite@8.2.0(@types/node@26.1.2): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 - postcss: 8.5.24 - rolldown: 1.1.5 + postcss: 8.5.25 + rolldown: 1.2.2 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 26.1.2 fsevents: 2.3.3 - vitest@4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1)(vite@8.1.5(@types/node@26.1.1)): + vitest@4.1.10(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.2.0(@types/node@26.1.2)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.1)) + '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@26.1.2)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -1895,15 +1821,15 @@ snapshots: picomatch: 4.0.5 std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 1.2.4 + tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@26.1.1) + vite: 8.2.0(@types/node@26.1.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 26.1.2 '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) - jsdom: 29.1.1 + jsdom: 30.0.1 transitivePeerDependencies: - msw @@ -1923,6 +1849,14 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + whatwg-url@17.1.0: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 From 37b7603b07b5b5fe806873bb2add7460d4f1f8a5 Mon Sep 17 00:00:00 2001 From: Paul Liu <20290410+Paulkm2006@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:16:45 +0800 Subject: [PATCH 3/6] chore: remove periods --- .../src/components/AdvancedSection.test.tsx | 2 +- frontend/src/components/MirrorSetting.tsx | 10 +-- frontend/src/components/RuntimePrompt.tsx | 2 +- frontend/src/components/RuntimeSection.tsx | 8 +- frontend/src/components/SecureKeyField.tsx | 2 +- frontend/src/i18n.tsx | 86 +++++++++---------- frontend/src/pages/ActivationPage.tsx | 6 +- frontend/src/pages/AgentProfilePage.tsx | 12 +-- frontend/src/pages/AgentSelectionPage.tsx | 6 +- .../src/pages/DesktopAgentSelectionPage.tsx | 4 +- .../src/pages/EnvironmentOverviewPage.tsx | 6 +- frontend/src/pages/ModelSelectionPage.tsx | 2 +- frontend/src/pages/ProfileSelectionPage.tsx | 2 +- frontend/src/pages/ProfilesPage.tsx | 10 +-- frontend/src/pages/ProviderKeyPage.tsx | 8 +- frontend/src/pages/ProvidersPage.tsx | 4 +- frontend/src/pages/ReviewPage.tsx | 2 +- .../testdata/status-empty-linux-arm64.json | 4 +- internal/catalog/public.go | 4 +- 19 files changed, 90 insertions(+), 90 deletions(-) diff --git a/frontend/src/components/AdvancedSection.test.tsx b/frontend/src/components/AdvancedSection.test.tsx index 47c07f3..47d49e9 100644 --- a/frontend/src/components/AdvancedSection.test.tsx +++ b/frontend/src/components/AdvancedSection.test.tsx @@ -5,7 +5,7 @@ import { AdvancedSection } from "./AdvancedSection"; function renderSection() { render( - + , diff --git a/frontend/src/components/MirrorSetting.tsx b/frontend/src/components/MirrorSetting.tsx index 3e601c4..9d7f3a8 100644 --- a/frontend/src/components/MirrorSetting.tsx +++ b/frontend/src/components/MirrorSetting.tsx @@ -64,10 +64,10 @@ export function MirrorSetting({ label }: { label?: string }) { // on a machine that is about to use the mirror would be worse than saying // nothing. const hint = fromRegion - ? t("已根据系统地区设置默认使用镜像。可以改回官方源。") + ? t("已根据系统地区设置默认使用镜像。可以改回官方源") : preferMirror - ? t("正在优先使用国内镜像。") - : t("默认使用官方源。国内网络较慢时可以改用镜像。"); + ? t("正在优先使用国内镜像") + : t("默认使用官方源。国内网络较慢时可以改用镜像"); return ( @@ -77,8 +77,8 @@ export function MirrorSetting({ label }: { label?: string }) { {t("优先使用国内镜像")} {fromRegion - ? t("已根据系统语言/地区自动开启。运行时仍校验固定哈希,npm 使用镜像 registry 的包元数据。") - : t("同时作用于运行时下载和 npm 安装的 Agent;运行时仍校验固定哈希并在下载失败时回退。Aider(uv)不受影响。")} + ? t("已根据系统语言/地区自动开启。运行时仍校验固定哈希,npm 使用镜像 registry 的包元数据") + : t("同时作用于运行时下载和 npm 安装的 Agent;运行时仍校验固定哈希并在下载失败时回退。Aider(uv)不受影响")} {t("需要先安装运行时")}
- {t("所选 Agent 通过 {runtimes} 安装,本机还没有。现在安装,或在激活时自动安装。", { runtimes: required.map((runtime) => runtime.name).join("、") })} + {t("所选 Agent 通过 {runtimes} 安装,本机还没有。现在安装,或在激活时自动安装", { runtimes: required.map((runtime) => runtime.name).join("、") })}
{required.map((runtime) => (
- {t("密钥只发送到当前本机服务,并保存在本机私有配置中。")} + {t("密钥只发送到当前本机服务,并保存在本机私有配置中")}
); } diff --git a/frontend/src/i18n.tsx b/frontend/src/i18n.tsx index 6072a27..420258e 100644 --- a/frontend/src/i18n.tsx +++ b/frontend/src/i18n.tsx @@ -65,7 +65,7 @@ const english = { "粘贴你的 API Key": "Paste your API key", "隐藏密钥": "Hide API key", "显示密钥": "Show API key", - "密钥只发送到当前本机服务,并保存在本机私有配置中。": "The key is sent only to the local service and stored in private local configuration.", + "密钥只发送到当前本机服务,并保存在本机私有配置中": "The key is sent only to the local service and stored in private local configuration", "激活步骤": "Setup steps", "已跳过": "Skipped", "配置": "Configure", @@ -108,8 +108,8 @@ const english = { "未配置": "Not configured", "{version}(锁定 {lockedVersion})": "{version} (locked to {lockedVersion})", "选择 Agent": "Select agents", - "选择要安装的桌面 Agent,每次安装一个。": "Choose the desktop agent to install. One per run.", - "安装应用后,会把选定的 Profile 应用到对应的配置。": "After installation, the selected profile is applied to the matching configuration.", + "选择要安装的桌面 Agent,每次安装一个": "Choose the desktop agent to install. One per run", + "安装应用后,会把选定的 Profile 应用到对应的配置": "After installation, the selected profile is applied to the matching configuration", "不支持": "Unsupported", "与 {name} 共用配置": "Shares configuration with {name}", "已安装,可直接应用 Profile": "Installed; a profile can be applied now", @@ -117,7 +117,7 @@ const english = { "检测到本机已有此应用": "This application is already installed", "选择配置模板": "Select a profile", "Profile选择": "Select profile", - "选择一个已有 Profile,或新建 Profile。": "Choose an existing profile or create a new one.", + "选择一个已有 Profile,或新建 Profile": "Choose an existing profile or create a new one", "新建 Profile": "Create profile", "创建 Profile": "Create profile", "选择一个 Profile": "Select a profile", @@ -126,37 +126,37 @@ const english = { "按引导安装桌面 Agent": "Follow the guide to install a desktop Agent", "确认安装": "Review installation", "将重新检查": "Recheck", - "选择这次要安装并配置的开发工具,每次安装一个。": "Choose the development tool to install and configure. One per run.", + "选择这次要安装并配置的开发工具,每次安装一个": "Choose the development tool to install and configure. One per run", "选择一个 Agent": "Select one agent", "正在检测本机环境": "Detecting the local environment", "常用 Agent": "Popular agents", - "可一键配置的默认安装最新版本,仅引导的只显示官方步骤。": "One-click agents install the latest version by default; guide-only agents show official steps.", + "可一键配置的默认安装最新版本,仅引导的只显示官方步骤": "One-click agents install the latest version by default; guide-only agents show official steps", "更多 Agent({count})": "More agents ({count})", "网关、平台账号与 IDE 扩展": "Gateways, platform accounts, and IDE extensions", "无法读取已保存的 API Key": "Could not read the saved API key", "连接测试失败": "Connection test failed", "无法打开注册页面": "Could not open the registration page", "连接模型服务": "Connect a model provider", - "Key 不会进入日志、URL 或前端持久化状态。": "The key is never written to logs, URLs, or persistent frontend state.", + "Key 不会进入日志、URL 或前端持久化状态": "The key is never written to logs, URLs, or persistent frontend state", "继续选择模型": "Continue to model selection", "注册并获取 Key": "Register and get a key", "自定义模型名称(可选)": "Custom model name (optional)", - "填写后将用此模型测试连接;留空时自动选择。": "When provided, this model is used for the connection test. Leave blank to select automatically.", - "可选,仅用于测试连接;实际配置模型在下一步选择。": "Optional; used only for the connection test. Choose the configured model in the next step.", + "填写后将用此模型测试连接;留空时自动选择": "When provided, this model is used for the connection test. Leave blank to select automatically", + "可选,仅用于测试连接;实际配置模型在下一步选择": "Optional; used only for the connection test. Choose the configured model in the next step", "测试连接": "Test connection", "修改这个 Agent 指向的 Provider 与模型": "Change which Provider and model this agent points at", - "留空则使用该 Provider 已保存的 Key。": "Leave blank to use the key already saved for this Provider.", - "未测试连接也可以应用,配置文件会先备份。": "You can apply without testing. The configuration file is backed up first.", - "连接测试通过后才能继续选择模型。": "Pass the connection test before selecting a model.", - "连接测试是可选的,可以直接继续选择模型。": "Connection testing is optional; you can continue to select a model directly.", + "留空则使用该 Provider 已保存的 Key": "Leave blank to use the key already saved for this Provider", + "未测试连接也可以应用,配置文件会先备份": "You can apply without testing. The configuration file is backed up first", + "连接测试通过后才能继续选择模型": "Pass the connection test before selecting a model", + "连接测试是可选的,可以直接继续选择模型": "Connection testing is optional; you can continue to select a model directly", "无法获取模型列表": "Could not load the model list", - "找到 {count} 个模型": "Found {count} models.", + "找到 {count} 个模型": "Found {count} models", "选择模型": "Select a model", - "从当前 Key 可访问的模型中选择,接口不支持时可直接输入模型 ID。": "Choose a model accessible with this key, or enter a model ID when discovery is unavailable.", + "从当前 Key 可访问的模型中选择,接口不支持时可直接输入模型 ID": "Choose a model accessible with this key, or enter a model ID when discovery is unavailable", "刷新列表": "Refresh list", "正在读取模型列表": "Loading models", "确认激活": "Review activation", - "核对安装、配置和备份范围。API Key 不会显示在此页。": "Review the installation, configuration, and backup scope. The API key is not shown here.", + "核对安装、配置和备份范围。API Key 不会显示在此页": "Review the installation, configuration, and backup scope. The API key is not shown here", "开始安装": "Start installation", "配置模板名称": "Profile name", "这次安装会保存为一个配置模板,之后可以直接应用": "This run is saved as a profile you can apply", @@ -181,17 +181,17 @@ const english = { "安装 Agent": "Install agent", "尚未安装任何 Agent": "No agents installed yet", "需要处理部分问题": "Some items need attention", - "安装请求同步执行,完成后将显示每个 Agent 的最终状态。": "Installation runs synchronously. Each agent's final status appears when it completes.", - "每个 Agent 的结果彼此独立,失败项可以单独重试。": "Each agent has an independent result. Failed items can be retried individually.", + "安装请求同步执行,完成后将显示每个 Agent 的最终状态": "Installation runs synchronously. Each agent's final status appears when it completes", + "每个 Agent 的结果彼此独立,失败项可以单独重试": "Each agent has an independent result. Failed items can be retried individually", "进入总览": "Open overview", "请保持此窗口打开": "Keep this window open", "下一步命令": "Next command", "环境总览": "Environment overview", "正在读取环境状态": "Loading environment status", - "本机已安装 Agent 及其当前配置。": "Installed agents and their current local configuration.", + "本机已安装 Agent 及其当前配置": "Installed agents and their current local configuration", "无法读取环境状态": "Could not load environment status", - "请刷新后重试。": "Refresh and try again.", - "本机已安装 Agent 及其当前 Provider、Profile 与模型。": "Installed agents and their current providers, profiles, and models.", + "请刷新后重试": "Refresh and try again", + "本机已安装 Agent 及其当前 Provider、Profile 与模型": "Installed agents and their current providers, profiles, and models", "刷新状态": "Refresh status", "桌面 Agent": "Desktop Agent", "与 {name} 共用配置文件 {path};安装和启动不会改动配置": "Shares the {path} configuration file with {name}; installation and launch do not modify it", @@ -221,8 +221,8 @@ const english = { "有新版本 {version},点击更新": "Version {version} is available; click to update", "无法更新 Agent": "Could not update the agent", "运行时": "Runtimes", - "缺少 {count} 个运行时,安装后即可自动安装对应 Agent。": "{count} runtime(s) missing. Install them to enable automatic agent installation.", - "Agent 安装所需的运行时都已就绪。": "Every runtime needed to install agents is ready.", + "缺少 {count} 个运行时,安装后即可自动安装对应 Agent": "{count} runtime(s) missing. Install them to enable automatic agent installation", + "Agent 安装所需的运行时都已就绪": "Every runtime needed to install agents is ready", "运行时安装失败": "Could not install the runtime", "版本 {version}": "Version {version}", "版本未知": "Version unknown", @@ -234,26 +234,26 @@ const english = { "安装": "Install", "安装 {name}": "Install {name}", "安装中": "Installing", - "运行时会安装到 {dir},并写入登录 PATH,不需要管理员权限。": "Runtimes install into {dir} and are added to your login PATH. No administrator rights needed.", - "运行时会安装到 OneAgent 的托管目录,并写入登录 PATH,不需要管理员权限。": "Runtimes install into OneAgent's managed directory and are added to your login PATH. No administrator rights needed.", + "运行时会安装到 {dir},并写入登录 PATH,不需要管理员权限": "Runtimes install into {dir} and are added to your login PATH. No administrator rights needed", + "运行时会安装到 OneAgent 的托管目录,并写入登录 PATH,不需要管理员权限": "Runtimes install into OneAgent's managed directory and are added to your login PATH. No administrator rights needed", "运行时下载": "Runtime downloads", "下载源": "Download source", "Agent 安装源": "Agent install source", - "默认使用官方源。国内网络较慢时可以改用镜像。": "Uses the official source by default. Switch to a mirror if it is slow from your network.", - "正在优先使用国内镜像。": "Downloading from a regional mirror first.", - "已根据系统地区设置默认使用镜像。可以改回官方源。": "A mirror is the default here based on your system region. You can switch back to the official source.", + "默认使用官方源。国内网络较慢时可以改用镜像": "Uses the official source by default. Switch to a mirror if it is slow from your network", + "正在优先使用国内镜像": "Downloading from a regional mirror first", + "已根据系统地区设置默认使用镜像。可以改回官方源": "A mirror is the default here based on your system region. You can switch back to the official source", "优先使用国内镜像": "Prefer a regional mirror", - "同时作用于运行时下载和 npm 安装的 Agent;运行时仍校验固定哈希并在下载失败时回退。Aider(uv)不受影响。": "Applies to runtime downloads and npm-installed agents. Runtime downloads still verify their pinned hash and fall back after a failed download. Aider (uv) is unaffected.", - "已根据系统语言/地区自动开启。运行时仍校验固定哈希,npm 使用镜像 registry 的包元数据。": "Enabled automatically from your system language and region. Runtimes still verify their pinned hash; npm uses package metadata from the mirror registry.", + "同时作用于运行时下载和 npm 安装的 Agent;运行时仍校验固定哈希并在下载失败时回退。Aider(uv)不受影响": "Applies to runtime downloads and npm-installed agents. Runtime downloads still verify their pinned hash and fall back after a failed download. Aider (uv) is unaffected", + "已根据系统语言/地区自动开启。运行时仍校验固定哈希,npm 使用镜像 registry 的包元数据": "Enabled automatically from your system language and region. Runtimes still verify their pinned hash; npm uses package metadata from the mirror registry", "无法保存下载设置": "Could not save the download setting", "需要先安装运行时": "A runtime is needed first", - "所选 Agent 通过 {runtimes} 安装,本机还没有。现在安装,或在激活时自动安装。": "The selected agents install through {runtimes}, which is not on this machine yet. Install it now, or let activation install it.", + "所选 Agent 通过 {runtimes} 安装,本机还没有。现在安装,或在激活时自动安装": "The selected agents install through {runtimes}, which is not on this machine yet. Install it now, or let activation install it", "安装 {name} {version}": "Install {name} {version}", "按引导安装命令行 Agent": "Follow the guide to install your first command-line agent", "找不到可配置的 Agent": "Configurable agent not found", "找不到 Agent": "Agent not found", - "{id} 不在可一键配置的范围内。": "{id} is not available for one-click setup.", - "未指定 Agent。": "No agent specified.", + "{id} 不在可一键配置的范围内": "{id} is not available for one-click setup", + "未指定 Agent": "No agent specified", "应用配置失败": "Could not apply configuration", "应用中": "Applying", "应用": "Apply", @@ -267,7 +267,7 @@ const english = { "当前指向 {target}。应用后会被替换,原文件会先备份到同目录的": "Currently points to {target}. Applying will replace it; the original file will first be backed up in the same directory as", "时间戳": "timestamp", "将测试 {protocol} 协议": "Testing the {protocol} protocol", - "可以指定具体模型。留空时由端点的模型列表自动选择,多数情况保持默认即可。": "Optionally choose a specific model. Leave blank for endpoint discovery; the default works in most cases.", + "可以指定具体模型。留空时由端点的模型列表自动选择,多数情况保持默认即可": "Optionally choose a specific model. Leave blank for endpoint discovery; the default works in most cases", "留空则由端点的模型列表自动选择": "Leave blank to select from the endpoint's model list", "快速小模型": "Fast small model", "找不到桌面 Agent": "Desktop agent not found", @@ -283,7 +283,7 @@ const english = { "{agents} 重新应用失败:{message}": "Could not reapply to {agents}: {message}", "删除 Provider“{name}”?": "Delete provider \"{name}\"?", "无法删除 Provider": "Could not delete provider", - "管理模型服务、端点与本机保存的 API Key。": "Manage model providers, endpoints, and locally saved API keys.", + "管理模型服务、端点与本机保存的 API Key": "Manage model providers, endpoints, and locally saved API keys", "编辑 {name}": "Edit {name}", "用户添加": "User added", "官网": "Website", @@ -297,27 +297,27 @@ const english = { "暂无 Agent 使用": "Not used by any agent", "已保存 Key": "Key saved", "未保存 Key": "No saved key", - "用户 Provider 的协议兼容性由你自己保证,OneAgent 不会为它降级或改写请求。": "You are responsible for custom provider protocol compatibility. OneAgent does not downgrade or rewrite requests.", + "用户 Provider 的协议兼容性由你自己保证,OneAgent 不会为它降级或改写请求": "You are responsible for custom provider protocol compatibility. OneAgent does not downgrade or rewrite requests", "无法保存 Profile": "Could not save profile", "应用 Profile 失败": "Could not apply profile", "{name} 已应用到 {count} 个 Agent": "Applied {name} to {count} agents", "无法应用 Profile": "Could not apply profile", - "在这里创建 Profile,再将它应用到所选 Agent。": "Create profiles here, then apply them to selected agents.", + "在这里创建 Profile,再将它应用到所选 Agent": "Create profiles here, then apply them to selected agents", "新增 Profile": "Add profile", - "将使用 Provider 已保存的 Key。": "Uses the provider's saved key.", - "这个 Provider 还没有 Key,先到 Provider 页面填写。": "This provider has no key yet. Add one on the Provider page.", + "将使用 Provider 已保存的 Key": "Uses the provider's saved key", + "这个 Provider 还没有 Key,先到 Provider 页面填写": "This provider has no key yet. Add one on the Provider page", "前往 Provider": "Open Provider", "保存 Profile": "Save profile", "删除 Profile": "Delete profile", "编辑 Profile": "Edit profile", - "为 {name} 选择关联的 Profile。": "Choose the profile linked to {name}.", - "这个 Provider 还没有 Key,请先到 Provider 页面填写。": "This provider has no key yet. Add one on the Provider page.", + "为 {name} 选择关联的 Profile": "Choose the profile linked to {name}", + "这个 Provider 还没有 Key,请先到 Provider 页面填写": "This provider has no key yet. Add one on the Provider page", "这个 Profile 还缺少 Provider Key 或模型": "This profile is missing a provider key or model", - "创建一个 Profile 后即可应用到这个 Agent。": "Create a profile, then apply it to this agent.", + "创建一个 Profile 后即可应用到这个 Agent": "Create a profile, then apply it to this agent", "未指定": "Not specified", "应用完成": "Applied", "还没有 Profile": "No profiles yet", - "走一遍安装引导,它会保存 Provider、模型和 API mode。": "Run the install guide; it saves the provider, model, and API mode.", + "走一遍安装引导,它会保存 Provider、模型和 API mode": "Run the install guide; it saves the provider, model, and API mode", "Provider 已有 Key": "Provider key saved", "Provider 缺少 Key": "Provider key missing", "未指定模型": "No model specified", diff --git a/frontend/src/pages/ActivationPage.tsx b/frontend/src/pages/ActivationPage.tsx index 26e725d..10bc2bb 100644 --- a/frontend/src/pages/ActivationPage.tsx +++ b/frontend/src/pages/ActivationPage.tsx @@ -8,8 +8,8 @@ import { DownloadProgress } from "../components/DownloadProgress"; import { LogDisclosure } from "../components/LogDisclosure"; import { PageScaffold } from "../components/PageScaffold"; import { useI18n } from "../i18n"; -import { taskCanceller, taskKey, type TaskCanceller, useTaskCenter, useTaskRoute } from "../state/TaskCenterContext"; import { desktopProtocol, profileAgentIdForDesktop, selectedDesktopApp } from "../state/desktopSetup"; +import { taskCanceller, taskKey, useTaskCenter, useTaskRoute, type TaskCanceller } from "../state/TaskCenterContext"; import { useWizard } from "../state/WizardContext"; import type { AgentInstallResult, InstallRequest } from "../types/api"; @@ -263,7 +263,7 @@ export function ActivationPage() { return ( navigate("/overview")} footerNote={loading ? t("请保持此窗口打开") : undefined} @@ -288,7 +288,7 @@ export function ActivationPage() { return ( navigate("/setup/review")} primaryLabel={allDone ? t("进入总览") : undefined} diff --git a/frontend/src/pages/AgentProfilePage.tsx b/frontend/src/pages/AgentProfilePage.tsx index 059b140..ce805a0 100644 --- a/frontend/src/pages/AgentProfilePage.tsx +++ b/frontend/src/pages/AgentProfilePage.tsx @@ -7,9 +7,9 @@ import { PageScaffold } from "../components/PageScaffold"; import { ProviderSegment } from "../components/ProviderSegment"; import { useI18n } from "../i18n"; import { desktopApps, desktopProfileUsable, desktopProfiles, desktopProtocol, profileAgentIdForDesktop } from "../state/desktopSetup"; -import { useWizard } from "../state/WizardContext"; import { byProfileCreatedAt, byProviderCreatedAt } from "../state/ranking"; -import type { ProfileSummary, ProviderId, ProtocolId } from "../types/api"; +import { useWizard } from "../state/WizardContext"; +import type { ProfileSummary, ProtocolId, ProviderId } from "../types/api"; interface ProfileDraft { id: string; @@ -166,7 +166,7 @@ export function AgentProfilePage() { return ( navigate("/overview")} primaryLabel={busy ? t("应用中") : t("应用")} @@ -216,9 +216,9 @@ export function AgentProfilePage() {

{status.providers[draft.provider]?.has_key - ? t("将使用 Provider 已保存的 Key。") + ? t("将使用 Provider 已保存的 Key") : <> - {t("这个 Provider 还没有 Key,请先到 Provider 页面填写。")} {" "} + {t("这个 Provider 还没有 Key,请先到 Provider 页面填写")} {" "} }

@@ -251,7 +251,7 @@ export function AgentProfilePage() { ) : (
{t("还没有可用的 Profile")} - {t("创建一个 Profile 后即可应用到这个 Agent。")} + {t("创建一个 Profile 后即可应用到这个 Agent")}
)}
diff --git a/frontend/src/pages/AgentSelectionPage.tsx b/frontend/src/pages/AgentSelectionPage.tsx index 51da82a..355ab2f 100644 --- a/frontend/src/pages/AgentSelectionPage.tsx +++ b/frontend/src/pages/AgentSelectionPage.tsx @@ -8,8 +8,8 @@ import { PageScaffold } from "../components/PageScaffold"; import { RuntimePrompt } from "../components/RuntimePrompt"; import { useI18n } from "../i18n"; import { byRank } from "../state/ranking"; -import type { AgentCatalogItem } from "../types/api"; import { useWizard } from "../state/WizardContext"; +import type { AgentCatalogItem } from "../types/api"; import { DesktopAgentSelectionPage } from "./DesktopAgentSelectionPage"; export function AgentSelectionPage() { @@ -52,7 +52,7 @@ export function AgentSelectionPage() { return (

{t("选择 Agent")}

-

{t("选择这次要安装并配置的开发工具,每次安装一个。")}

+

{t("选择这次要安装并配置的开发工具,每次安装一个")}

{/* Guide-only rows stay selectable: install_many answers them with a guide-only result and writes nothing. */}
diff --git a/frontend/src/pages/DesktopAgentSelectionPage.tsx b/frontend/src/pages/DesktopAgentSelectionPage.tsx index f06cd22..7efcacc 100644 --- a/frontend/src/pages/DesktopAgentSelectionPage.tsx +++ b/frontend/src/pages/DesktopAgentSelectionPage.tsx @@ -37,7 +37,7 @@ export function DesktopAgentSelectionPage() { return (

{t("桌面 Agent")}

-

{t("安装应用后,会把选定的 Profile 应用到对应的配置。")}

+

{t("安装应用后,会把选定的 Profile 应用到对应的配置")}