diff --git a/install b/install index f5444c351..e695d02a1 100755 --- a/install +++ b/install @@ -132,7 +132,7 @@ else needs_baseline=false if [ "$arch" = "x64" ]; then if [ "$os" = "linux" ]; then - if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then + if ! grep -qwi avx2 /proc/cpuinfo 2>/dev/null; then needs_baseline=true fi fi @@ -143,6 +143,20 @@ else needs_baseline=true fi fi + + if [ "$os" = "windows" ]; then + ps="(Add-Type -MemberDefinition \"[DllImport(\"\"kernel32.dll\"\")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);\" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)" + out="" + if command -v powershell.exe >/dev/null 2>&1; then + out=$(powershell.exe -NoProfile -NonInteractive -Command "$ps" 2>/dev/null || true) + elif command -v pwsh >/dev/null 2>&1; then + out=$(pwsh -NoProfile -NonInteractive -Command "$ps" 2>/dev/null || true) + fi + out=$(echo "$out" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]') + if [ "$out" != "true" ] && [ "$out" != "1" ]; then + needs_baseline=true + fi + fi fi target="$os-$arch" diff --git a/package.json b/package.json index 92e5773ba..627198d2a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "AI-powered development tool", "private": true, "type": "module", - "packageManager": "bun@1.3.5", + "packageManager": "bun@1.3.9", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "dev:desktop": "bun --cwd packages/desktop tauri dev", diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 842433891..52c9007ea 100644 --- a/packages/app/e2e/selectors.ts +++ b/packages/app/e2e/selectors.ts @@ -10,8 +10,11 @@ export const settingsNotificationsAgentSelector = '[data-action="settings-notifi export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]' export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]' export const settingsSoundsAgentSelector = '[data-action="settings-sounds-agent"]' +export const settingsSoundsAgentEnabledSelector = '[data-action="settings-sounds-agent-enabled"]' export const settingsSoundsPermissionsSelector = '[data-action="settings-sounds-permissions"]' +export const settingsSoundsPermissionsEnabledSelector = '[data-action="settings-sounds-permissions-enabled"]' export const settingsSoundsErrorsSelector = '[data-action="settings-sounds-errors"]' +export const settingsSoundsErrorsEnabledSelector = '[data-action="settings-sounds-errors-enabled"]' export const settingsUpdatesStartupSelector = '[data-action="settings-updates-startup"]' export const settingsReleaseNotesSelector = '[data-action="settings-release-notes"]' diff --git a/packages/app/e2e/settings/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts index 42534968b..9fbcf79f5 100644 --- a/packages/app/e2e/settings/settings.spec.ts +++ b/packages/app/e2e/settings/settings.spec.ts @@ -9,6 +9,7 @@ import { settingsNotificationsPermissionsSelector, settingsReleaseNotesSelector, settingsSoundsAgentSelector, + settingsSoundsAgentEnabledSelector, settingsSoundsErrorsSelector, settingsSoundsPermissionsSelector, settingsThemeSelector, @@ -335,6 +336,30 @@ test("changing sound agent selection persists in localStorage", async ({ page, g expect(stored?.sounds?.agent).not.toBe("staplebops-01") }) +test("disabling agent sound disables sound selection", async ({ page, gotoSession }) => { + await gotoSession() + + const dialog = await openSettings(page) + const select = dialog.locator(settingsSoundsAgentSelector) + const switchContainer = dialog.locator(settingsSoundsAgentEnabledSelector) + const trigger = select.locator('[data-slot="select-select-trigger"]') + await expect(select).toBeVisible() + await expect(switchContainer).toBeVisible() + await expect(trigger).toBeEnabled() + + await switchContainer.locator('[data-slot="switch-control"]').click() + await page.waitForTimeout(100) + + await expect(trigger).toBeDisabled() + + const stored = await page.evaluate((key) => { + const raw = localStorage.getItem(key) + return raw ? JSON.parse(raw) : null + }, settingsKey) + + expect(stored?.sounds?.agentEnabled).toBe(false) +}) + test("changing permissions and errors sounds updates localStorage", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 4b4382eb5..676648688 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -103,6 +103,24 @@ export function DialogConnectProvider(props: { provider: string }) { return value.label ?? "" } + function formatError(value: unknown, fallback: string): string { + if (value && typeof value === "object" && "data" in value) { + const data = (value as { data?: { message?: unknown } }).data + if (typeof data?.message === "string" && data.message) return data.message + } + if (value && typeof value === "object" && "error" in value) { + const nested = formatError((value as { error?: unknown }).error, "") + if (nested) return nested + } + if (value && typeof value === "object" && "message" in value) { + const message = (value as { message?: unknown }).message + if (typeof message === "string" && message) return message + } + if (value instanceof Error && value.message) return value.message + if (typeof value === "string" && value) return value + return fallback + } + async function selectMethod(index: number) { if (timer.current !== undefined) { clearTimeout(timer.current) @@ -141,7 +159,7 @@ export function DialogConnectProvider(props: { provider: string }) { }) .catch((e) => { if (!alive.value) return - dispatch({ type: "auth.error", error: String(e) }) + dispatch({ type: "auth.error", error: formatError(e, language.t("common.requestFailed")) }) }) } } @@ -330,8 +348,7 @@ export function DialogConnectProvider(props: { provider: string }) { await complete() return } - const message = result.error instanceof Error ? result.error.message : String(result.error) - setFormStore("error", message || language.t("provider.connect.oauth.code.invalid")) + setFormStore("error", formatError(result.error, language.t("provider.connect.oauth.code.invalid"))) } return ( @@ -387,7 +404,7 @@ export function DialogConnectProvider(props: { provider: string }) { if (!alive.value) return if (!result.ok) { - const message = result.error instanceof Error ? result.error.message : String(result.error) + const message = formatError(result.error, language.t("common.requestFailed")) dispatch({ type: "auth.error", error: message }) return } diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index f35d0564c..29a3666c0 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -347,9 +347,9 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil tabs().open(value) file.load(path) if (!view().reviewPanel.opened()) view().reviewPanel.open() - layout.fileTree.open() layout.fileTree.setTab("all") props.onOpenFile?.(path) + tabs().setActive(value) } const handleSelect = (item: Entry | undefined) => { diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 731c9aa1e..f9c686288 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -158,14 +158,13 @@ export const PromptInput: Component = (props) => { const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path)) if (wantsReview) { if (!view().reviewPanel.opened()) view().reviewPanel.open() - layout.fileTree.open() layout.fileTree.setTab("changes") + tabs().setActive("review") requestAnimationFrame(() => comments.setFocus(focus)) return } if (!view().reviewPanel.opened()) view().reviewPanel.open() - layout.fileTree.open() layout.fileTree.setTab("all") const tab = files.tab(item.path) tabs().open(tab) diff --git a/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app/src/components/prompt-input/slash-popover.tsx index 554a15bb7..259883d61 100644 --- a/packages/app/src/components/prompt-input/slash-popover.tsx +++ b/packages/app/src/components/prompt-input/slash-popover.tsx @@ -53,18 +53,15 @@ export const PromptPopover: Component = (props) => { > {(item) => { - const active = props.atActive === props.atKey(item) - const shared = { - "w-full flex items-center gap-x-2 rounded-md px-2 py-0.5": true, - "bg-surface-raised-base-hover": active, - } + const key = props.atKey(item) if (item.type === "agent") { return (