From badf91101babf96fa09cb79a8294f7e23b9f081c Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:54:02 -0700 Subject: [PATCH] fix(quality): enforce performance-safe lint baseline (#11074) * fix(quality): clear safe existing lint findings * fix(quality): keep lint cleanup allocation-free * fix(quality): enforce performance-safe baseline * test(terminal): drain deferred confirmation cleanup --- .github/workflows/pr.yml | 3 + config/oxlint-code-quality-type-aware.json | 11 +- config/oxlint-code-quality.json | 17 +- .../scripts/claude-usage-yield-benchmark.mjs | 3 +- .../cli-runtime-client-deferral-benchmark.mjs | 1 + .../linux-wayland-terminal-exercise.mjs | 2 +- ...node-pty-console-list-agent-patch.test.mjs | 5 +- .../publish-complete-draft-releases.mjs | 2 +- config/scripts/run-idle-cpu-benchmark.mjs | 2 +- .../app/h/[hostId]/session/[worktreeId].tsx | 7 +- mobile/app/h/[hostId]/tasks.tsx | 6 +- .../session/use-pr-bot-author-overrides.ts | 3 +- package.json | 6 +- src/cli/args.ts | 27 +-- src/cli/browser.test.ts | 2 +- src/cli/command-spec.ts | 18 ++ src/cli/command-suggestion.ts | 3 +- src/cli/handlers/agent-hooks.ts | 8 +- src/cli/handlers/environment.ts | 3 +- src/cli/handlers/vm.ts | 6 +- src/cli/runtime/transport.ts | 2 +- .../remote-hook-service-installers.test.ts | 39 ++-- .../session-scanner-opencode-sqlite-list.ts | 6 +- .../ai-vault/session-scanner-record-value.ts | 5 + .../ai-vault/session-scanner-token-values.ts | 2 +- src/main/ai-vault/session-scanner-types.ts | 2 +- src/main/ai-vault/session-scanner-values.ts | 9 +- src/main/browser/agent-browser-bridge.test.ts | 82 +++++---- src/main/browser/browser-cookie-import.ts | 18 +- src/main/browser/browser-manager.ts | 7 +- .../browser/cdp-bridge-integration.test.ts | 2 +- src/main/claude-accounts/keychain.ts | 4 +- src/main/cli/cli-installer.ts | 2 +- .../codex/hook-service-trust-grant.test.ts | 4 +- .../daemon/degraded-daemon-pty-provider.ts | 12 +- src/main/daemon/history-manager.ts | 2 +- src/main/github/pr-head-tracking-ref.ts | 2 +- src/main/gitlab/mr-head-tracking-ref.ts | 2 +- src/main/gitlab/work-item-details.ts | 2 +- src/main/index.ts | 18 +- src/main/ipc/ai-vault.ts | 27 ++- src/main/ipc/diagnostics.ts | 3 +- src/main/ipc/ephemeral-vm.ts | 8 +- src/main/ipc/github.ts | 4 +- src/main/ipc/pty.test.ts | 6 +- src/main/ipc/register-core-handlers.ts | 7 +- src/main/ipc/ssh-browse.ts | 2 +- src/main/ipc/ssh.test.ts | 2 +- src/main/ipc/ssh.ts | 3 +- src/main/ipc/telemetry.ts | 3 +- src/main/ipc/worktree-remote.ts | 10 +- src/main/persistence.ts | 19 +- .../plugins/plugin-content-pack-registry.ts | 9 +- src/main/providers/local-pty-provider.ts | 3 +- src/main/providers/ssh-git-provider.test.ts | 8 +- src/main/rate-limits/claude-fetcher.ts | 4 +- src/main/runtime/orca-runtime-files.ts | 2 +- src/main/runtime/orca-runtime.ts | 170 +++++++++--------- src/main/runtime/orchestration/coordinator.ts | 4 +- .../runtime/rpc/methods/agent-session.test.ts | 6 +- src/main/runtime/rpc/methods/ai-vault.ts | 3 +- .../methods/file-watch-stream-lifecycle.ts | 4 +- src/main/runtime/rpc/methods/native-chat.ts | 7 +- src/main/runtime/runtime-rpc.test.ts | 4 +- src/main/ssh/relay-protocol.ts | 2 +- src/main/ssh/ssh-connection.test.ts | 2 +- src/main/ssh/ssh-connection.ts | 2 - src/preload/api-types.ts | 24 ++- src/preload/index.ts | 43 +++-- src/relay/protocol.ts | 2 +- src/relay/relay.ts | 2 +- .../NewWorkspaceComposerCard.test.tsx | 3 +- .../components/NewWorkspaceComposerCard.tsx | 9 +- src/renderer/src/components/TaskPage.tsx | 4 +- .../AgentSessionContinuationDialog.test.tsx | 3 +- .../use-hold-dictation-gesture.test.tsx | 3 +- .../editor/ChangesModeView.test.tsx | 3 +- .../src/components/editor/MonacoEditor.tsx | 3 +- .../editor/ReviewNotesSendMenuContent.tsx | 7 +- .../editor/markdown-round-trip.test.ts | 6 +- .../editor/useEditorPanelContentState.ts | 2 +- .../use-feature-wall-tour-telemetry.ts | 6 +- .../FloatingTerminalWindowControls.tsx | 9 +- .../slug-dialog/SlugDialogBody.tsx | 3 +- .../components/mobile/MobilePageContent.tsx | 10 +- ...-native-chat-composer-attachments.test.tsx | 3 +- .../use-native-chat-composer-paste.test.tsx | 3 +- ...-native-chat-external-attachments.test.tsx | 3 +- .../use-onboarding-flow-persistence.test.ts | 3 +- .../SourceControlAgentActionDialog.test.tsx | 3 +- .../right-sidebar/checks-panel-content.tsx | 4 +- .../src/components/right-sidebar/index.tsx | 3 +- ...ce-control-create-review-blocked-action.ts | 1 + .../source-control-entry-context-menu.tsx | 5 +- .../use-hosted-review-actions.ts | 3 +- .../useCreatePullRequestDialogFields.test.ts | 3 +- .../useCreatePullRequestDialogFields.ts | 3 +- .../settings/MobileEmulatorSettingsPane.tsx | 2 +- .../settings/RepositoryHooksSection.test.ts | 3 +- .../settings/TerminalAppearanceSection.tsx | 3 +- .../settings/VoiceSpeechModelSection.tsx | 7 +- .../repository-source-control-ai-labels.ts | 6 +- .../settings/settings-search-keywords.ts | 3 +- .../components/sidebar/SidebarNav.test.tsx | 3 +- ....compact-ports-hover-independence.test.tsx | 3 +- .../components/sidebar/WorktreeOpenInMenu.tsx | 7 +- .../src/components/status-bar/tooltip.tsx | 82 +++++---- .../components/tab-bar/QuickLaunchButton.tsx | 9 +- .../src/components/tab-bar/TabBar.tsx | 2 +- .../src/components/tab-group/tab-drag-data.ts | 56 ++++++ .../tab-group/tab-drag-preview-target.ts | 3 +- .../src/components/tab-group/tab-drop-zone.ts | 2 +- .../tab-group/tab-group-panel-split-target.ts | 2 +- .../src/components/tab-group/tab-insertion.ts | 2 +- .../components/tab-group/useTabDragSplit.ts | 88 ++------- .../terminal-pane/keyboard-handlers.ts | 1 + .../terminal-pane/pty-connection.test.ts | 2 +- .../useSessionRestoredBannerDismiss.test.tsx | 3 +- .../TerminalQuickCommandDialog.tsx | 4 +- src/renderer/src/hooks/useIpcEvents.ts | 6 +- src/renderer/src/lib/agent-paste-draft.ts | 3 +- .../src/lib/ephemeral-vm-workspace-target.ts | 6 +- .../lib/github-work-item-background-create.ts | 3 +- .../src/lib/open-in-application-selection.ts | 3 + .../lib/pane-manager/pane-manager-types.ts | 3 +- src/renderer/src/lib/repo-slug-index.ts | 3 +- .../src/lib/worktree-operation-generation.ts | 6 +- .../src/runtime/web-runtime-session.ts | 6 +- .../github-pr-refresh-host-guard.test.ts | 7 +- .../src/store/slices/runtime-status.test.ts | 7 +- src/renderer/src/web/web-preload-api.ts | 2 +- src/shared/agent-hook-listener.ts | 2 + src/shared/agent-session-resume.ts | 1 + src/shared/ai-vault-types.ts | 2 + src/shared/ephemeral-vm-recipes.test.ts | 6 +- src/shared/ephemeral-vm-recipes.ts | 10 -- src/shared/external-worktree-inbox.ts | 2 +- src/shared/external-worktree-visibility.ts | 25 +++ .../remote-runtime-shared-control-protocol.ts | 3 +- src/shared/runtime-types.ts | 4 +- src/shared/worktree-ownership.ts | 34 +--- .../fixtures/visible-tui-scroll-fixture.cjs | 2 +- ...codex-skill-preview-artifact-repro.spec.ts | 3 +- 143 files changed, 679 insertions(+), 623 deletions(-) create mode 100644 src/cli/command-spec.ts create mode 100644 src/main/ai-vault/session-scanner-record-value.ts create mode 100644 src/renderer/src/components/tab-group/tab-drag-data.ts create mode 100644 src/renderer/src/lib/open-in-application-selection.ts diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 330657566..f51e54b96 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -32,6 +32,9 @@ jobs: - name: Lint run: pnpm exec oxlint --format github + - name: Enforce full code-quality baseline + run: pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware + - name: Check switch exhaustiveness run: pnpm run lint:switch-exhaustiveness diff --git a/config/oxlint-code-quality-type-aware.json b/config/oxlint-code-quality-type-aware.json index 409215908..9198df657 100644 --- a/config/oxlint-code-quality-type-aware.json +++ b/config/oxlint-code-quality-type-aware.json @@ -12,11 +12,16 @@ }, "rules": { "typescript/await-thenable": "warn", - "typescript/no-floating-promises": "warn", - "typescript/no-misused-promises": "warn", - "typescript/only-throw-error": "warn", "typescript/restrict-plus-operands": "warn", "typescript/restrict-template-expressions": "warn" }, + "overrides": [ + { + "files": ["**/*.test.*", "**/*.spec.*"], + "rules": { + "typescript/await-thenable": "off" + } + } + ], "ignorePatterns": ["**/node_modules", "**/dist", "**/out"] } diff --git a/config/oxlint-code-quality.json b/config/oxlint-code-quality.json index 3ab7f3bae..bfa1c115c 100644 --- a/config/oxlint-code-quality.json +++ b/config/oxlint-code-quality.json @@ -10,9 +10,6 @@ "restriction": "off", "nursery": "off" }, - "options": { - "reportUnusedDisableDirectives": "warn" - }, "jsPlugins": [ { "name": "app-store-performance", @@ -29,15 +26,7 @@ "import/no-cycle": ["warn", { "maxDepth": 3 }], "import/no-duplicates": "warn", "import/no-self-import": "warn", - "max-params": ["warn", 5], - "no-fallthrough": "warn", - "no-loop-func": "warn", - "no-promise-executor-return": "warn", - "no-unmodified-loop-condition": "warn", - "preserve-caught-error": "warn", - "promise/no-multiple-resolved": "warn", - "react/no-unstable-nested-components": "warn", - "react/react-compiler": "warn" + "no-fallthrough": "warn" }, "overrides": [ { @@ -55,9 +44,7 @@ "rules": { "vitest/no-conditional-tests": "warn", "vitest/no-focused-tests": "warn", - "vitest/no-identical-title": "warn", - "vitest/valid-expect": "warn", - "vitest/valid-title": "warn" + "vitest/no-identical-title": "warn" } }, { diff --git a/config/scripts/claude-usage-yield-benchmark.mjs b/config/scripts/claude-usage-yield-benchmark.mjs index c58747626..aea601c58 100644 --- a/config/scripts/claude-usage-yield-benchmark.mjs +++ b/config/scripts/claude-usage-yield-benchmark.mjs @@ -12,11 +12,10 @@ // other work would be a regression, not a win. // // Run with: node config/scripts/claude-usage-yield-benchmark.mjs -import { readFileSync } from 'node:fs' +import { readFileSync, readdirSync, statSync } from 'node:fs' import { homedir } from 'node:os' import { join } from 'node:path' import { performance } from 'node:perf_hooks' -import { readdirSync, statSync } from 'node:fs' const REPO_ROOT = new URL('../..', import.meta.url) const ROUNDS = Number(process.env.ORCA_YIELD_BENCH_ROUNDS ?? '10') diff --git a/config/scripts/cli-runtime-client-deferral-benchmark.mjs b/config/scripts/cli-runtime-client-deferral-benchmark.mjs index 4a7560fc7..c322410b2 100644 --- a/config/scripts/cli-runtime-client-deferral-benchmark.mjs +++ b/config/scripts/cli-runtime-client-deferral-benchmark.mjs @@ -196,6 +196,7 @@ try { // Each case is (label, argv, env). The runtime-dependent ones point at an // empty user-data dir so both arms get the same deterministic answer. const isolated = { ORCA_USER_DATA_PATH: userDataPath } + /** @type {Array<[string, string[], Record]>} */ const cases = [ ['orca --help', ['--help'], {}], ['orca help worktree', ['help', 'worktree'], {}], diff --git a/config/scripts/linux-wayland-terminal-exercise.mjs b/config/scripts/linux-wayland-terminal-exercise.mjs index 65ae598db..4a6b942f9 100644 --- a/config/scripts/linux-wayland-terminal-exercise.mjs +++ b/config/scripts/linux-wayland-terminal-exercise.mjs @@ -200,7 +200,7 @@ export async function setupTerminal(page, repoPath, logPhase) { return pane?.container?.dataset?.ptyId ?? null }) ) - logPhase('setup.pty-bound', `ptyId=${ptyId}`) + logPhase('setup.pty-bound', `ptyId=${String(ptyId)}`) return ptyId } diff --git a/config/scripts/node-pty-console-list-agent-patch.test.mjs b/config/scripts/node-pty-console-list-agent-patch.test.mjs index c6c8677a6..07e97c548 100644 --- a/config/scripts/node-pty-console-list-agent-patch.test.mjs +++ b/config/scripts/node-pty-console-list-agent-patch.test.mjs @@ -40,7 +40,10 @@ describe('Windows SSH relay node-pty console-list patch', () => { const tamperedPatch = writeNodePtyFixture('1.1.0', publishedAgentSource()) patchNodePtyConsoleListAgent(tamperedPatch.root) - writeFileSync(tamperedPatch.agentPath, `${readFileSync(tamperedPatch.agentPath)}\n// drift`) + writeFileSync( + tamperedPatch.agentPath, + `${readFileSync(tamperedPatch.agentPath, 'utf8')}\n// drift` + ) expect(() => assertPatchedNodePtyConsoleListAgent(tamperedPatch.root)).toThrow('not installed') }) }) diff --git a/config/scripts/publish-complete-draft-releases.mjs b/config/scripts/publish-complete-draft-releases.mjs index a02431afe..ad66c8cfb 100644 --- a/config/scripts/publish-complete-draft-releases.mjs +++ b/config/scripts/publish-complete-draft-releases.mjs @@ -98,7 +98,7 @@ export async function publishCompleteDraftReleases({ for (const release of candidates) { const tag = release.tag_name - if (!(await isDraftBuiltFromCurrentRef({ tag, release }))) { + if (!(await Promise.resolve(isDraftBuiltFromCurrentRef({ tag, release })))) { const reason = 'tag is not built from the current release ref' skipped.push({ tag, reason }) log(`Skipping stale RC draft release ${tag}: ${reason}`) diff --git a/config/scripts/run-idle-cpu-benchmark.mjs b/config/scripts/run-idle-cpu-benchmark.mjs index 2e5112b01..baa061b35 100644 --- a/config/scripts/run-idle-cpu-benchmark.mjs +++ b/config/scripts/run-idle-cpu-benchmark.mjs @@ -572,7 +572,7 @@ async function main() { if (options.output) { mkdirSync(path.dirname(path.resolve(options.output)), { recursive: true }) writeFileSync(options.output, `${JSON.stringify(report, null, 2)}\n`) - console.log(`[idle-cpu] wrote ${options.output}`) + console.log(`[idle-cpu] wrote ${String(options.output)}`) } console.log( JSON.stringify( diff --git a/mobile/app/h/[hostId]/session/[worktreeId].tsx b/mobile/app/h/[hostId]/session/[worktreeId].tsx index 0b4b11b44..7e4ac1793 100644 --- a/mobile/app/h/[hostId]/session/[worktreeId].tsx +++ b/mobile/app/h/[hostId]/session/[worktreeId].tsx @@ -1,7 +1,9 @@ import { useState, useEffect, useRef, useCallback, useMemo } from 'react' -import { Animated, AppState, Linking, type AppStateStatus } from 'react-native' -import * as Clipboard from 'expo-clipboard' import { + Animated, + AppState, + Linking, + type AppStateStatus, BackHandler, FlatList, Image, @@ -17,6 +19,7 @@ import { type LayoutChangeEvent, type ListRenderItem } from 'react-native' +import * as Clipboard from 'expo-clipboard' import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router' import AsyncStorage from '@react-native-async-storage/async-storage' diff --git a/mobile/app/h/[hostId]/tasks.tsx b/mobile/app/h/[hostId]/tasks.tsx index a9d24ae63..f3980bb31 100644 --- a/mobile/app/h/[hostId]/tasks.tsx +++ b/mobile/app/h/[hostId]/tasks.tsx @@ -106,11 +106,9 @@ import { } from '../../../src/tasks/setup-hook-trust' import { colors, radii, spacing, typography } from '../../../src/theme/mobile-theme' import { triggerMediumImpact } from '../../../src/platform/haptics' -import type { - GitHubProjectSortDirection, - GitHubProjectTable as SharedGitHubProjectTable -} from '../../../src/tasks/mobile-github-project-group-sort' import { + type GitHubProjectSortDirection, + type GitHubProjectTable as SharedGitHubProjectTable, groupRows, isIterationCurrent, sortRows, diff --git a/mobile/src/session/use-pr-bot-author-overrides.ts b/mobile/src/session/use-pr-bot-author-overrides.ts index 4d9166c39..94e397946 100644 --- a/mobile/src/session/use-pr-bot-author-overrides.ts +++ b/mobile/src/session/use-pr-bot-author-overrides.ts @@ -1,6 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react' -import type { ConnectionState } from '../transport/types' -import type { RpcSuccess } from '../transport/types' +import type { ConnectionState, RpcSuccess } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' import { createBotAuthorOverrideSet } from '../../../src/shared/pr-bot-author-overrides' diff --git a/package.json b/package.json index 2e9bf6d16..e21ee6be8 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "main": "./out/main/index.js", "scripts": { "format": "oxfmt --write .", - "lint": "oxlint && pnpm run lint:switch-exhaustiveness && node config/scripts/check-styled-scrollbars.mjs && pnpm run check:quadratic-buffer-concat && pnpm run check:reliability-gates && pnpm run check:max-lines-ratchet && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalog && pnpm run verify:localization-coverage", + "lint": "oxlint && pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run lint:switch-exhaustiveness && node config/scripts/check-styled-scrollbars.mjs && pnpm run check:quadratic-buffer-concat && pnpm run check:reliability-gates && pnpm run check:max-lines-ratchet && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalog && pnpm run verify:localization-coverage", "audit:code-quality": "pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run audit:react-doctor", - "audit:code-quality:native": "oxlint --config config/oxlint-code-quality.json --report-unused-disable-directives-severity warn src config tests mobile", - "audit:code-quality:type-aware": "oxlint --type-aware --config config/oxlint-code-quality-type-aware.json src config tests mobile", + "audit:code-quality:native": "oxlint --config config/oxlint-code-quality.json src config tests mobile --deny-warnings", + "audit:code-quality:type-aware": "oxlint --type-aware --config config/oxlint-code-quality-type-aware.json src config tests --deny-warnings", "audit:react-doctor": "pnpm dlx react-doctor@0.9.1 . --yes --no-supply-chain --no-telemetry --blocking none", "check:code-quality:changed": "node config/scripts/check-changed-code-quality.mjs", "check:react-doctor:changed": "node config/scripts/check-react-doctor-changed.mjs", diff --git a/src/cli/args.ts b/src/cli/args.ts index 90a42ac81..3d37517e7 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -1,5 +1,9 @@ import { RuntimeClientError } from './runtime/types' import { unknownCommandData, unknownFlagData } from './command-suggestion' +import { specPaths, type CommandSpec } from './command-spec' + +export { specPaths } +export type { CommandSpec } export type ParsedArgs = { commandPath: string[] @@ -7,23 +11,6 @@ export type ParsedArgs = { positionalFlagConflicts?: string[] } -export type CommandSpec = { - path: string[] - // Why: conventional alternate verbs should resolve without duplicating specs - // or handler registrations. - aliases?: string[][] - argumentMode?: 'parsed' | 'passthrough' - // Why: irreversibly destroys persistent state — typo recovery must not steer a - // benign mistake into one of these via the agent nextSteps channel. #6303 - destructive?: boolean - summary: string - usage: string - allowedFlags: string[] - positionalArgs?: string[] - examples?: string[] - notes?: string[] -} - export const GLOBAL_FLAGS = ['help', 'json', 'pairing-code', 'environment'] const GLOBAL_VALUE_FLAGS = new Set(['pairing-code', 'environment']) export const BOOLEAN_FLAGS = new Set([ @@ -157,12 +144,6 @@ export function matches(actual: string[], expected: string[]): boolean { ) } -// Why: a spec is reachable by its canonical path plus any declared aliases — one -// definition so resolution, validation, help, and agent-context never disagree. -export function specPaths(spec: CommandSpec): string[][] { - return spec.aliases ? [spec.path, ...spec.aliases] : [spec.path] -} - export function supportsBrowserPageFlag(commandPath: string[]): boolean { const joined = commandPath.join(' ') if (['open', 'status'].includes(commandPath[0])) { diff --git a/src/cli/browser.test.ts b/src/cli/browser.test.ts index 83ca43d48..f622f9d79 100644 --- a/src/cli/browser.test.ts +++ b/src/cli/browser.test.ts @@ -293,7 +293,7 @@ describe('orca cli browser page targeting', () => { }) }) -describe('orca cli browser tab profiles', () => { +describe('orca cli browser profile management', () => { beforeEach(() => { callMock.mockReset() }) diff --git a/src/cli/command-spec.ts b/src/cli/command-spec.ts new file mode 100644 index 000000000..1719fbd96 --- /dev/null +++ b/src/cli/command-spec.ts @@ -0,0 +1,18 @@ +export type CommandSpec = { + path: string[] + // Why: conventional alternate verbs should resolve without duplicating specs or handlers. + aliases?: string[][] + argumentMode?: 'parsed' | 'passthrough' + // Why: typo recovery must never steer a benign mistake into destructive state changes. + destructive?: boolean + summary: string + usage: string + allowedFlags: string[] + positionalArgs?: string[] + examples?: string[] + notes?: string[] +} + +export function specPaths(spec: CommandSpec): string[][] { + return spec.aliases ? [spec.path, ...spec.aliases] : [spec.path] +} diff --git a/src/cli/command-suggestion.ts b/src/cli/command-suggestion.ts index 530d9baf7..db481de6e 100644 --- a/src/cli/command-suggestion.ts +++ b/src/cli/command-suggestion.ts @@ -1,5 +1,4 @@ -import type { CommandSpec } from './args' -import { specPaths } from './args' +import { specPaths, type CommandSpec } from './command-spec' // Why: rank the live registry so typo recovery cannot drift from accepted paths. diff --git a/src/cli/handlers/agent-hooks.ts b/src/cli/handlers/agent-hooks.ts index 2b83f5c15..03f9c24c9 100644 --- a/src/cli/handlers/agent-hooks.ts +++ b/src/cli/handlers/agent-hooks.ts @@ -4,7 +4,12 @@ import { dirname, join } from 'node:path' import { randomUUID } from 'node:crypto' import type { CommandHandler } from '../dispatch' import { printResult } from '../format' -import { RuntimeClientError, type RuntimeClient, type RuntimeRpcSuccess } from '../runtime-client' +import { + RuntimeClientError, + type RuntimeClient, + type RuntimeRpcSuccess, + getDefaultUserDataPath +} from '../runtime-client' import type { AgentHookInstallStatus } from '../../shared/agent-hook-types' import { getDefaultPersistedState } from '../../shared/constants' import type { PersistedState } from '../../shared/types' @@ -12,7 +17,6 @@ import { applyAgentStatusHooksEnabled, getManagedAgentHookStatuses } from '../../main/agent-hooks/managed-agent-hook-controls' -import { getDefaultUserDataPath } from '../runtime-client' type AgentHookCommandResult = { enabled: boolean diff --git a/src/cli/handlers/environment.ts b/src/cli/handlers/environment.ts index 08530ea29..7827f9134 100644 --- a/src/cli/handlers/environment.ts +++ b/src/cli/handlers/environment.ts @@ -1,8 +1,7 @@ import type { CommandHandler } from '../dispatch' import { formatEnvironment, formatEnvironmentList, printResult } from '../format' -import { getDefaultUserDataPath } from '../runtime-client' +import { getDefaultUserDataPath, RuntimeClientError } from '../runtime-client' import type { RuntimeRpcSuccess } from '../runtime-client' -import { RuntimeClientError } from '../runtime-client' import { redactRuntimeEnvironment } from '../../shared/runtime-environments' import { addEnvironmentFromPairingCode, diff --git a/src/cli/handlers/vm.ts b/src/cli/handlers/vm.ts index aaf01b088..e000ec15a 100644 --- a/src/cli/handlers/vm.ts +++ b/src/cli/handlers/vm.ts @@ -5,11 +5,13 @@ import { RuntimeClientError } from '../runtime-client' import { parseOrcaYaml } from '../../shared/orca-yaml' import { getEphemeralVmRecipeResultProjectRoot, - getEphemeralVmRecipeResultWarnings, - redactEphemeralVmRecipeDiagnosticText, type EphemeralVmRecipeDoctorCheck, type EphemeralVmRecipeDoctorResult } from '../../shared/ephemeral-vm-recipes' +import { + getEphemeralVmRecipeResultWarnings, + redactEphemeralVmRecipeDiagnosticText +} from '../../shared/ephemeral-vm-recipe-diagnostics' // Why: import directly from the doctor module (not the barrel) — it uses Node // fs/path and must stay out of the browser bundle that imports the barrel. import { doctorEphemeralVmRecipe } from '../../shared/ephemeral-vm-recipe-doctor' diff --git a/src/cli/runtime/transport.ts b/src/cli/runtime/transport.ts index fc6c3339d..b88f3cf32 100644 --- a/src/cli/runtime/transport.ts +++ b/src/cli/runtime/transport.ts @@ -88,7 +88,7 @@ export async function sendRequest( ) }) }) - socket.on('data', (chunk) => { + socket.on('data', (chunk: string) => { buffer += chunk // Why: the server may interleave `{"_keepalive":true}\n` frames with the // final success/failure frame to keep both idle timers alive during a diff --git a/src/main/agent-hooks/remote-hook-service-installers.test.ts b/src/main/agent-hooks/remote-hook-service-installers.test.ts index 3acca6a64..89124fe86 100644 --- a/src/main/agent-hooks/remote-hook-service-installers.test.ts +++ b/src/main/agent-hooks/remote-hook-service-installers.test.ts @@ -7,33 +7,20 @@ vi.mock('electron', () => ({ } })) -import { CodexHookService } from '../codex/hook-service' -import { DroidHookService } from '../droid/hook-service' -import { CursorHookService } from '../cursor/hook-service' -import { CommandCodeHookService } from '../command-code/hook-service' -import { GeminiHookService } from '../gemini/hook-service' -import { AntigravityHookService } from '../antigravity/hook-service' -import { AmpHookService } from '../amp/hook-service' -import { ClaudeHookService } from '../claude/hook-service' -import { GrokHookService } from '../grok/hook-service' -import { CopilotHookService } from '../copilot/hook-service' -import { HermesHookService } from '../hermes/hook-service' -import { DevinHookService } from '../devin/hook-service' -import { KimiHookService } from '../kimi/hook-service' +import { CodexHookService, codexHookService } from '../codex/hook-service' +import { DroidHookService, droidHookService } from '../droid/hook-service' +import { CursorHookService, cursorHookService } from '../cursor/hook-service' +import { CommandCodeHookService, commandCodeHookService } from '../command-code/hook-service' +import { GeminiHookService, geminiHookService } from '../gemini/hook-service' +import { AntigravityHookService, antigravityHookService } from '../antigravity/hook-service' +import { AmpHookService, ampHookService } from '../amp/hook-service' +import { ClaudeHookService, claudeHookService } from '../claude/hook-service' +import { GrokHookService, grokHookService } from '../grok/hook-service' +import { CopilotHookService, copilotHookService } from '../copilot/hook-service' +import { HermesHookService, hermesHookService } from '../hermes/hook-service' +import { DevinHookService, devinHookService } from '../devin/hook-service' +import { KimiHookService, kimiHookService } from '../kimi/hook-service' import { openClaudeHookService } from '../openclaude/hook-service' -import { ampHookService } from '../amp/hook-service' -import { antigravityHookService } from '../antigravity/hook-service' -import { claudeHookService } from '../claude/hook-service' -import { codexHookService } from '../codex/hook-service' -import { copilotHookService } from '../copilot/hook-service' -import { cursorHookService } from '../cursor/hook-service' -import { droidHookService } from '../droid/hook-service' -import { commandCodeHookService } from '../command-code/hook-service' -import { geminiHookService } from '../gemini/hook-service' -import { devinHookService } from '../devin/hook-service' -import { grokHookService } from '../grok/hook-service' -import { hermesHookService } from '../hermes/hook-service' -import { kimiHookService } from '../kimi/hook-service' import { MANAGED_AGENT_HOOK_INSTALLERS } from './managed-agent-hook-controls' import { installRemoteManagedAgentHooks, diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-list.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-list.ts index b89b97b22..5d6c62028 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite-list.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-list.ts @@ -1,6 +1,8 @@ import type { AiVaultAgent, AiVaultScanIssue } from '../../shared/ai-vault-types' -import { buildOpenCodeSqliteCandidatePath } from './session-scanner-opencode-sqlite-paths' -import { splitOpenCodeSqliteCandidate } from './session-scanner-opencode-sqlite-paths' +import { + buildOpenCodeSqliteCandidatePath, + splitOpenCodeSqliteCandidate +} from './session-scanner-opencode-sqlite-paths' import type { SessionFileCandidate } from './session-scanner-types' import { errorMessage } from './session-scanner-values' import SyncDatabase from '../sqlite/sync-database' diff --git a/src/main/ai-vault/session-scanner-record-value.ts b/src/main/ai-vault/session-scanner-record-value.ts new file mode 100644 index 000000000..bd2c9b632 --- /dev/null +++ b/src/main/ai-vault/session-scanner-record-value.ts @@ -0,0 +1,5 @@ +export function asRecord(value: unknown): Record | null { + return value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : null +} diff --git a/src/main/ai-vault/session-scanner-token-values.ts b/src/main/ai-vault/session-scanner-token-values.ts index e2621b9f4..0c30558ff 100644 --- a/src/main/ai-vault/session-scanner-token-values.ts +++ b/src/main/ai-vault/session-scanner-token-values.ts @@ -1,5 +1,5 @@ import type { CodexUsageSnapshot } from './session-scanner-types' -import { asRecord } from './session-scanner-values' +import { asRecord } from './session-scanner-record-value' export function tokenTotal(value: unknown): number { const usage = asRecord(value) diff --git a/src/main/ai-vault/session-scanner-types.ts b/src/main/ai-vault/session-scanner-types.ts index f9af3f208..ac94a067d 100644 --- a/src/main/ai-vault/session-scanner-types.ts +++ b/src/main/ai-vault/session-scanner-types.ts @@ -1,5 +1,5 @@ -import type { AiVaultAgent } from '../../shared/ai-vault-types' import type { + AiVaultAgent, AiVaultScanIssue, AiVaultSession, AiVaultSessionPreviewMessage diff --git a/src/main/ai-vault/session-scanner-values.ts b/src/main/ai-vault/session-scanner-values.ts index 39006bd68..1deb8bf5d 100644 --- a/src/main/ai-vault/session-scanner-values.ts +++ b/src/main/ai-vault/session-scanner-values.ts @@ -1,6 +1,9 @@ import { homedir } from 'node:os' import { basename, dirname, join } from 'node:path' import { readFile } from 'node:fs/promises' +import { asRecord } from './session-scanner-record-value' + +export { asRecord } export function timestampMs(value: unknown): number { if (typeof value === 'string') { @@ -25,12 +28,6 @@ export function parseJsonObject(line: string): Record | null { } } -export function asRecord(value: unknown): Record | null { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : null -} - export function extractString(value: unknown): string | null { if (typeof value !== 'string') { return null diff --git a/src/main/browser/agent-browser-bridge.test.ts b/src/main/browser/agent-browser-bridge.test.ts index dc804a2db..b9f5a9bd9 100644 --- a/src/main/browser/agent-browser-bridge.test.ts +++ b/src/main/browser/agent-browser-bridge.test.ts @@ -63,6 +63,8 @@ import { CLIPBOARD_TEXT_WRITE_TOO_LARGE_ERROR } from '../../shared/clipboard-text' +type ExecFileCallback = (error: unknown, stdout?: string, stderr?: string) => void + // Why: the bridge resolves webContents via dynamic require('electron').webContents.fromId // inside a try/catch. Override the private method to inject our mock. // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -116,31 +118,37 @@ function mockWebContents(id: number, url = 'https://example.com', title = 'Examp } function succeedWith(data: unknown): void { - execFileMock.mockImplementation((_bin: string, _args: string[], _opts: unknown, cb: Function) => { - cb(null, JSON.stringify({ success: true, data }), '') - return { - stdin: { on: vi.fn(), end: (text: string) => stdinWrites.push(text) } + execFileMock.mockImplementation( + (_bin: string, _args: string[], _opts: unknown, cb: ExecFileCallback) => { + cb(null, JSON.stringify({ success: true, data }), '') + return { + stdin: { on: vi.fn(), end: (text: string) => stdinWrites.push(text) } + } } - }) + ) } function succeedForContentEditable(data: unknown = { ok: true }): void { - execFileMock.mockImplementation((_bin: string, args: string[], _opts: unknown, cb: Function) => { - const result = - args.includes('get') && args.includes('attr') && args.includes('contenteditable') - ? { value: 'true' } - : data - cb(null, JSON.stringify({ success: true, data: result }), '') - return { - stdin: { on: vi.fn(), end: (text: string) => stdinWrites.push(text) } + execFileMock.mockImplementation( + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { + const result = + args.includes('get') && args.includes('attr') && args.includes('contenteditable') + ? { value: 'true' } + : data + cb(null, JSON.stringify({ success: true, data: result }), '') + return { + stdin: { on: vi.fn(), end: (text: string) => stdinWrites.push(text) } + } } - }) + ) } function failWith(error: string): void { - execFileMock.mockImplementation((_bin: string, _args: string[], _opts: unknown, cb: Function) => { - cb(null, JSON.stringify({ success: false, error }), '') - }) + execFileMock.mockImplementation( + (_bin: string, _args: string[], _opts: unknown, cb: ExecFileCallback) => { + cb(null, JSON.stringify({ success: false, error }), '') + } + ) } class TestEvent { @@ -352,7 +360,7 @@ describe('AgentBrowserBridge', () => { try { const closeKill = vi.fn() execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { return { kill: closeKill } } @@ -445,7 +453,7 @@ describe('AgentBrowserBridge', () => { let releaseSnapshot: (() => void) | null = null const activeChild = { kill: vi.fn() } execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('snapshot')) { releaseSnapshot = () => { cb(null, JSON.stringify({ success: false, error: CDP_DISCOVERY_FAILURE }), '') @@ -486,7 +494,7 @@ describe('AgentBrowserBridge', () => { it('handles malformed JSON from agent-browser', async () => { execFileMock.mockImplementation( - (_bin: string, _args: string[], _opts: unknown, cb: Function) => { + (_bin: string, _args: string[], _opts: unknown, cb: ExecFileCallback) => { cb(null, 'not json at all', '') } ) @@ -748,7 +756,7 @@ describe('AgentBrowserBridge', () => { const commandCalls: string[][] = [] execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { commandCalls.push(args) cb(null, JSON.stringify({ success: true, data: { ok: true } }), '') } @@ -782,7 +790,7 @@ describe('AgentBrowserBridge', () => { let releaseSnapshot: (() => void) | null = null execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { cb(null, JSON.stringify({ success: true, data: null }), '') return @@ -877,7 +885,7 @@ describe('AgentBrowserBridge', () => { const commandCalls: string[][] = [] execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { commandCalls.push(args) cb(null, JSON.stringify({ success: true, data: { ok: true } }), '') } @@ -1024,7 +1032,7 @@ describe('AgentBrowserBridge', () => { let releaseFirstScreenshot: (() => void) | null = null execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { cb(null, JSON.stringify({ success: true, data: null }), '') return @@ -1103,7 +1111,7 @@ describe('AgentBrowserBridge', () => { webContentsFromIdMock.mockReturnValue(wc) execFileMock.mockImplementation( - (_bin: string, _args: string[], _opts: unknown, cb: Function) => { + (_bin: string, _args: string[], _opts: unknown, cb: ExecFileCallback) => { cb(null, JSON.stringify({ success: true, data: null }), '') } ) @@ -1137,7 +1145,7 @@ describe('AgentBrowserBridge', () => { const killedError = Object.assign(new Error('timeout'), { killed: true }) execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { cb(null, JSON.stringify({ success: true, data: null }), '') return @@ -1167,7 +1175,7 @@ describe('AgentBrowserBridge', () => { const commandCalls: string[][] = [] let releaseDestroyClose: (() => void) | null = null execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { commandCalls.push(args) if (args.includes('close')) { if (!releaseDestroyClose) { @@ -1212,7 +1220,7 @@ describe('AgentBrowserBridge', () => { const commandCalls: string[][] = [] let releaseStaleClose: (() => void) | null = null execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { commandCalls.push(args) if (args.includes('close') && !releaseStaleClose) { releaseStaleClose = () => { @@ -1270,7 +1278,7 @@ describe('AgentBrowserBridge', () => { } execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('snapshot')) { resolveRunningCommand = () => cb(killedError, '', '') return activeChild @@ -1354,7 +1362,7 @@ describe('AgentBrowserBridge', () => { const commandCalls: string[][] = [] execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { commandCalls.push(args) cb(null, JSON.stringify({ success: true, data: { ok: true } }), '') } @@ -1390,7 +1398,7 @@ describe('AgentBrowserBridge', () => { const commandCalls: string[][] = [] execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { commandCalls.push(args) cb(null, JSON.stringify({ success: true, data: { ok: true } }), '') } @@ -1577,7 +1585,7 @@ describe('AgentBrowserBridge', () => { let releaseSnapshot: (() => void) | null = null execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { cb(null, JSON.stringify({ success: true, data: null }), '') return @@ -1875,7 +1883,7 @@ describe('AgentBrowserBridge', () => { let helperSessionIsStale = false execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { cb(null, JSON.stringify({ success: true, data: null }), '') } else if (args.includes('snapshot')) { @@ -2549,7 +2557,7 @@ describe('AgentBrowserBridge', () => { it('returns browser_timeout for timed conditional waits without recycling the session', async () => { const killedError = Object.assign(new Error('timeout'), { killed: true }) execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('wait')) { cb(killedError, '', '') return @@ -2573,7 +2581,7 @@ describe('AgentBrowserBridge', () => { it('passes stderr through as error message on execFile failure', async () => { execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { cb(null, JSON.stringify({ success: true, data: null }), '') return @@ -2586,7 +2594,7 @@ describe('AgentBrowserBridge', () => { it('falls back to error.message when stderr is empty', async () => { execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: Function) => { + (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { if (args.includes('close')) { cb(null, JSON.stringify({ success: true, data: null }), '') return @@ -2601,7 +2609,7 @@ describe('AgentBrowserBridge', () => { it('returns browser_error with truncated output for malformed JSON', async () => { execFileMock.mockImplementation( - (_bin: string, _args: string[], _opts: unknown, cb: Function) => { + (_bin: string, _args: string[], _opts: unknown, cb: ExecFileCallback) => { cb(null, 'Error: not json output', '') } ) diff --git a/src/main/browser/browser-cookie-import.ts b/src/main/browser/browser-cookie-import.ts index e7703a1f5..22b891cd2 100644 --- a/src/main/browser/browser-cookie-import.ts +++ b/src/main/browser/browser-cookie-import.ts @@ -596,7 +596,7 @@ async function importValidatedCookies( } } diag( - ` cookie.set FAILED: domain=${cookie.domain} name=${cookie.name} valLen=${val.length} badChar=${badInfo} err=${err}` + ` cookie.set FAILED: domain=${cookie.domain} name=${cookie.name} valLen=${val.length} badChar=${badInfo} err=${String(err)}` ) } } @@ -1019,7 +1019,7 @@ function getWindowsEncryptionKey(browser: DetectedBrowser): EncryptionKeyResult return { key: Buffer.from(result, 'base64'), mode: 'aes-256-gcm' } } catch (err) { - diag(` Windows DPAPI key extraction failed: ${err}`) + diag(` Windows DPAPI key extraction failed: ${String(err)}`) return null } } @@ -1339,7 +1339,7 @@ async function importCookiesFromFirefox( return importValidatedCookies(validated, rows.length, targetPartition) } catch (err) { rmSync(tmpDir, { recursive: true, force: true }) - diag(` Firefox import failed: ${err}`) + diag(` Firefox import failed: ${String(err)}`) return { ok: false, reason: 'Could not import cookies from Firefox. Try closing Firefox first.' @@ -1361,7 +1361,7 @@ async function importCookiesFromSafari( try { data = readFileSync(browser.cookiesPath) } catch (err) { - diag(` Safari read failed: ${err}`) + diag(` Safari read failed: ${String(err)}`) // Why: Safari's Cookies.binarycookies is in a sandbox container; reading it needs Full Disk Access. const isPermError = err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'EPERM' @@ -1392,7 +1392,7 @@ async function importCookiesFromSafari( return importValidatedCookies(valid, cookies.length, targetPartition) } catch (err) { - diag(` Safari import failed: ${err}`) + diag(` Safari import failed: ${String(err)}`) return { ok: false, reason: 'Could not import cookies from Safari.' } } } @@ -1480,7 +1480,7 @@ export async function importCookiesFromBrowser( } catch { /* best-effort */ } - diag(` Chromium snapshot failed: ${err}`) + diag(` Chromium snapshot failed: ${String(err)}`) return { ok: false, reason: `Could not copy ${browser.label} cookies database. Try closing ${browser.label} first.` @@ -1527,7 +1527,7 @@ export async function importCookiesFromBrowser( placeholders = targetCols.map(() => '?').join(', ') stagingDb.exec('DELETE FROM cookies') } catch (err) { - diag(` staging database unusable, restart fallback disabled: ${err}`) + diag(` staging database unusable, restart fallback disabled: ${String(err)}`) stagingAvailable = false targetColumnInfo = null colList = null @@ -1807,7 +1807,7 @@ export async function importCookiesFromBrowser( } catch { /* may not exist yet */ } - diag(` SQLite import failed: ${err}`) + diag(` SQLite import failed: ${String(err)}`) return { ok: false, reason: reasonWithDiagLog( @@ -1818,7 +1818,7 @@ export async function importCookiesFromBrowser( try { sourceSnapshot.cleanup() } catch (err) { - diag(` Chromium snapshot cleanup failed: ${err}`) + diag(` Chromium snapshot cleanup failed: ${String(err)}`) } } } diff --git a/src/main/browser/browser-manager.ts b/src/main/browser/browser-manager.ts index bd0e5698c..c6dfab887 100644 --- a/src/main/browser/browser-manager.ts +++ b/src/main/browser/browser-manager.ts @@ -43,14 +43,17 @@ import { buildBrowserIframeClickedLinkRoutingScript } from './browser-clicked-link-routing' import { cleanElectronUserAgent } from './browser-session-ua' -import type { BrowserViewportOverride } from '../../shared/types' +import type { + BrowserViewportOverride, + BrowserCertificateFailure, + BrowserLoadError +} from '../../shared/types' import { type BrowserAnnotationViewportBridgeOptions, BROWSER_ANNOTATION_VIEWPORT_BRIDGE_WORLD_ID, buildBrowserAnnotationViewportBridgeScript } from '../../shared/browser-annotation-viewport-bridge' import type { KeybindingOverrides } from '../../shared/keybindings' -import type { BrowserCertificateFailure, BrowserLoadError } from '../../shared/types' import { BrowserCertificateTrustController, type ManagedBrowserGuestContext diff --git a/src/main/browser/cdp-bridge-integration.test.ts b/src/main/browser/cdp-bridge-integration.test.ts index daaae6054..b3b1fae94 100644 --- a/src/main/browser/cdp-bridge-integration.test.ts +++ b/src/main/browser/cdp-bridge-integration.test.ts @@ -250,7 +250,7 @@ async function sendRequest( let buffer = '' socket.setEncoding('utf8') socket.once('error', reject) - socket.on('data', (chunk) => { + socket.on('data', (chunk: string) => { buffer += chunk const newlineIndex = buffer.indexOf('\n') if (newlineIndex === -1) { diff --git a/src/main/claude-accounts/keychain.ts b/src/main/claude-accounts/keychain.ts index 910e1786a..49d89c33d 100644 --- a/src/main/claude-accounts/keychain.ts +++ b/src/main/claude-accounts/keychain.ts @@ -170,9 +170,9 @@ function isKeychainNotFoundError(error: unknown): boolean { : undefined const message = error && typeof error === 'object' - ? `${(error as { stderr?: unknown }).stderr ?? ''} ${ + ? `${String((error as { stderr?: unknown }).stderr ?? '')} ${String( (error as { message?: unknown }).message ?? '' - }`.toLowerCase() + )}`.toLowerCase() : String(error).toLowerCase() return code === 44 || message.includes('could not be found') || message.includes('not be found') } diff --git a/src/main/cli/cli-installer.ts b/src/main/cli/cli-installer.ts index 18d2fcc8f..9c120c230 100644 --- a/src/main/cli/cli-installer.ts +++ b/src/main/cli/cli-installer.ts @@ -654,7 +654,7 @@ export class CliInstaller { private isWindowsPackagedBundledCommand( commandPath: string | null, launcherPath: string | null - ): commandPath is string { + ): boolean { return ( this.platform === 'win32' && this.isPackaged && diff --git a/src/main/codex/hook-service-trust-grant.test.ts b/src/main/codex/hook-service-trust-grant.test.ts index 9e28dbbdc..7814994c7 100644 --- a/src/main/codex/hook-service-trust-grant.test.ts +++ b/src/main/codex/hook-service-trust-grant.test.ts @@ -8,12 +8,12 @@ import { rmSync, symlinkSync, statSync, - writeFileSync + writeFileSync, + existsSync } from 'node:fs' import { tmpdir } from 'node:os' import type * as Os from 'node:os' import { join } from 'node:path' -import { existsSync } from 'node:fs' import { wrapPosixHookCommand } from '../agent-hooks/installer-utils' import { computeTrustKey, diff --git a/src/main/daemon/degraded-daemon-pty-provider.ts b/src/main/daemon/degraded-daemon-pty-provider.ts index 8ca0f39bd..d0d748a9b 100644 --- a/src/main/daemon/degraded-daemon-pty-provider.ts +++ b/src/main/daemon/degraded-daemon-pty-provider.ts @@ -2,9 +2,15 @@ import type { DaemonPtyAdapter } from './daemon-pty-adapter' import { combineUnsubscribes } from './combine-unsubscribes' import { shutdownDegradedFallbackSessions } from './degraded-daemon-fallback-shutdown' import { inspectPtyProviderProcess } from '../providers/pty-process-inspection' -import type { IPtyProvider, PtyBackgroundStreamEvent } from '../providers/types' -import type { PtyDataEvent, PtyProviderBufferSnapshot } from '../providers/types' -import type { PtyProcessInfo, PtySpawnOptions, PtySpawnResult } from '../providers/types' +import type { + IPtyProvider, + PtyBackgroundStreamEvent, + PtyDataEvent, + PtyProviderBufferSnapshot, + PtyProcessInfo, + PtySpawnOptions, + PtySpawnResult +} from '../providers/types' export class DegradedDaemonPtyProvider implements IPtyProvider { readonly routesFreshSpawnsToLocalProvider = true diff --git a/src/main/daemon/history-manager.ts b/src/main/daemon/history-manager.ts index fb574dd64..126df0593 100644 --- a/src/main/daemon/history-manager.ts +++ b/src/main/daemon/history-manager.ts @@ -340,7 +340,7 @@ export class HistoryManager { private async waitForSessionMutations(sessionId: string): Promise { while (this.pendingSessionMutations.has(sessionId)) { - await Promise.allSettled(this.pendingSessionMutations.get(sessionId) ?? []) + await Promise.allSettled(this.pendingSessionMutations.get(sessionId)!) } } } diff --git a/src/main/github/pr-head-tracking-ref.ts b/src/main/github/pr-head-tracking-ref.ts index 84788b6c0..9ffa73742 100644 --- a/src/main/github/pr-head-tracking-ref.ts +++ b/src/main/github/pr-head-tracking-ref.ts @@ -44,7 +44,7 @@ export async function fetchGitHubPullRequestHeadRef( options: { localGitExecOptions?: LocalGitExecOptions } = {} ): Promise { if (!isValidReviewHeadNumber(prNumber)) { - throw new Error(`Invalid pull request number: ${prNumber}`) + throw new Error(`Invalid pull request number: ${String(prNumber)}`) } if (!isSafeReviewHeadFetchRemote(remote)) { throw new Error('Pull request fetch remote must not start with "-".') diff --git a/src/main/gitlab/mr-head-tracking-ref.ts b/src/main/gitlab/mr-head-tracking-ref.ts index fa3540e12..e68a8235a 100644 --- a/src/main/gitlab/mr-head-tracking-ref.ts +++ b/src/main/gitlab/mr-head-tracking-ref.ts @@ -25,7 +25,7 @@ export async function fetchGitLabMergeRequestHeadRef( options: { localGitExecOptions?: LocalGitExecOptions } = {} ): Promise { if (!isValidReviewHeadNumber(mrIid)) { - throw new Error(`Invalid merge request iid: ${mrIid}`) + throw new Error(`Invalid merge request iid: ${String(mrIid)}`) } if (!isSafeReviewHeadFetchRemote(remote)) { throw new Error('Merge request fetch remote must not start with "-".') diff --git a/src/main/gitlab/work-item-details.ts b/src/main/gitlab/work-item-details.ts index 0b37206d6..066e539b8 100644 --- a/src/main/gitlab/work-item-details.ts +++ b/src/main/gitlab/work-item-details.ts @@ -10,6 +10,7 @@ import type { GitLabPipelineJob, GitLabWorkItem, GitLabWorkItemDetails, + IssueSourcePreference, MRComment } from '../../shared/types' import { mapIssueToWorkItem, mapMRToWorkItem } from './mappers' @@ -24,7 +25,6 @@ import { type LocalGitExecOptions, type ProjectRef } from './gl-utils' -import type { IssueSourcePreference } from '../../shared/types' function encodedProject(projectPath: string): string { return encodeURIComponent(projectPath) diff --git a/src/main/index.ts b/src/main/index.ts index a76f6f50d..ceb1bf618 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -20,7 +20,15 @@ import { StatsCollector, initStatsPath } from './stats/collector' import { ClaudeUsageStore, initClaudeUsagePath } from './claude-usage/store' import { CodexUsageStore, initCodexUsagePath } from './codex-usage/store' import { OpenCodeUsageStore, initOpenCodeUsagePath } from './opencode-usage/store' -import { killAllPty } from './ipc/pty' +import { + killAllPty, + clearProviderPtyState, + getPtyIdForPaneKey, + registerPaneKeyTeardownListener, + getLocalPtyProvider, + getSshPtyProvider, + registerHeadlessPtyRuntime +} from './ipc/pty' import { initDaemonPtyProvider, disconnectDaemon, shutdownDaemon } from './daemon/daemon-init' import { closeAllWatchers } from './ipc/filesystem-watcher' import { disposeWorktreeBaseDirectoryWatchers } from './ipc/worktree-base-directory-watcher' @@ -203,14 +211,6 @@ import { setDefaultWslDistroOverride } from './git/runner' import { getRepoIdFromWorktreeId } from '../shared/worktree-id' import { parseWorkspaceKey } from '../shared/workspace-scope' import { setMigrationUnsupportedPtyListener } from './agent-hooks/migration-unsupported-pty-state' -import { - clearProviderPtyState, - getPtyIdForPaneKey, - registerPaneKeyTeardownListener, - getLocalPtyProvider, - getSshPtyProvider, - registerHeadlessPtyRuntime -} from './ipc/pty' import { AgentBrowserBridge } from './browser/agent-browser-bridge' import { EmulatorBridge } from './emulator/emulator-bridge' import { browserCertificateTrustController, browserManager } from './browser/browser-manager' diff --git a/src/main/ipc/ai-vault.ts b/src/main/ipc/ai-vault.ts index ebfd52e63..0e4496290 100644 --- a/src/main/ipc/ai-vault.ts +++ b/src/main/ipc/ai-vault.ts @@ -121,21 +121,18 @@ async function scanAiVaultSessionsByHostScope( if (executionHostScope === 'all') { const runtimeHosts = getActiveRuntimeAiVaultHostInfosResult() const runtimeResults = runtimeHosts.issue ? [runtimeHosts.issue] : [] - return mergeAiVaultListResults( - await Promise.all([ - scanLocalAiVaultSessions(args), - ...getActiveSshAiVaultHostInfos().map((hostInfo) => - scanSshAiVaultSessions(hostInfo.targetId, args) - ), - ...runtimeHosts.hostInfos.map((hostInfo) => - scanRuntimeAiVaultSessions(hostInfo, args, { - timeoutMs: AI_VAULT_ALL_HOST_RUNTIME_TIMEOUT_MS - }) - ), - ...runtimeResults - ]), - args?.limit - ) + const scannedResults = await Promise.all([ + scanLocalAiVaultSessions(args), + ...getActiveSshAiVaultHostInfos().map((hostInfo) => + scanSshAiVaultSessions(hostInfo.targetId, args) + ), + ...runtimeHosts.hostInfos.map((hostInfo) => + scanRuntimeAiVaultSessions(hostInfo, args, { + timeoutMs: AI_VAULT_ALL_HOST_RUNTIME_TIMEOUT_MS + }) + ) + ]) + return mergeAiVaultListResults([...scannedResults, ...runtimeResults], args?.limit) } const parsed = parseExecutionHostId(executionHostScope) diff --git a/src/main/ipc/diagnostics.ts b/src/main/ipc/diagnostics.ts index 53d8d2f6e..5446f2aca 100644 --- a/src/main/ipc/diagnostics.ts +++ b/src/main/ipc/diagnostics.ts @@ -20,8 +20,7 @@ import { app, dialog, ipcMain, shell } from 'electron' import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs' -import { arch as osArch, platform as osPlatform, release as osRelease } from 'node:os' -import { tmpdir } from 'node:os' +import { arch as osArch, platform as osPlatform, release as osRelease, tmpdir } from 'node:os' import { join } from 'node:path' import { collectDiagnosticBundle, diff --git a/src/main/ipc/ephemeral-vm.ts b/src/main/ipc/ephemeral-vm.ts index f75c9c77d..593607d4a 100644 --- a/src/main/ipc/ephemeral-vm.ts +++ b/src/main/ipc/ephemeral-vm.ts @@ -2,11 +2,13 @@ import { app, ipcMain } from 'electron' import type { Store } from '../persistence' import { getEphemeralVmRecipeResultConnection, - getEphemeralVmRecipeResultWarnings, - redactEphemeralVmRecipeDiagnosticText, - type EphemeralVmRecipeResultWarning, type EphemeralVmRecipeDoctorResult } from '../../shared/ephemeral-vm-recipes' +import { + getEphemeralVmRecipeResultWarnings, + redactEphemeralVmRecipeDiagnosticText, + type EphemeralVmRecipeResultWarning +} from '../../shared/ephemeral-vm-recipe-diagnostics' // Why: import directly from the doctor module (not the barrel) — it uses Node // fs/path and must stay out of the browser bundle that imports the barrel. import { doctorEphemeralVmRecipe } from '../../shared/ephemeral-vm-recipe-doctor' diff --git a/src/main/ipc/github.ts b/src/main/ipc/github.ts index a01225bfc..6fcc53bab 100644 --- a/src/main/ipc/github.ts +++ b/src/main/ipc/github.ts @@ -13,7 +13,8 @@ import type { GitHubPRRefreshCandidate, GitHubPRRefreshEnqueueResult, GitHubPRRefreshReason, - PRRefreshOutcome + PRRefreshOutcome, + GitHubPRFile } from '../../shared/types' import { getRepoExecutionHostId } from '../../shared/execution-host' import type { TaskSourceContext } from '../../shared/task-source-context' @@ -68,7 +69,6 @@ import { type PRRefreshValidationDenialReason } from '../github/pr-refresh-validation-backoff' import { getLocalProjectWorktreeGitOptions } from '../project-runtime-git-options' -import type { GitHubPRFile } from '../../shared/types' import { dispatchWorkItem, type WorkItemArgs } from './github-work-item-args' import { getProjectViewTable, diff --git a/src/main/ipc/pty.test.ts b/src/main/ipc/pty.test.ts index 2e706452f..58b841508 100644 --- a/src/main/ipc/pty.test.ts +++ b/src/main/ipc/pty.test.ts @@ -205,7 +205,10 @@ vi.mock('../codex/codex-pane-account-registry', () => ({ recordCodexPaneAccount: recordCodexPaneAccountMock, forgetCodexPaneAccount: forgetCodexPaneAccountMock })) -import { LocalPtyProvider } from '../providers/local-pty-provider' +import { + LocalPtyProvider, + _resetLocalPtyProviderStateForTest +} from '../providers/local-pty-provider' import { makePaneKey } from '../../shared/stable-pane-id' import { SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV } from '../../shared/setup-agent-sequencing' import { @@ -228,7 +231,6 @@ import { restorePtyIncarnation, type PrepareCodexSessionResume } from './pty' -import { _resetLocalPtyProviderStateForTest } from '../providers/local-pty-provider' import { resetMacosLoginShellPreflightForTests } from '../providers/macos-tcc-login-shell' import { _resetHiddenRendererPtyDeliveryGateForTest, diff --git a/src/main/ipc/register-core-handlers.ts b/src/main/ipc/register-core-handlers.ts index 51cc0d75d..ef903dcae 100644 --- a/src/main/ipc/register-core-handlers.ts +++ b/src/main/ipc/register-core-handlers.ts @@ -34,7 +34,11 @@ import { registerDashboardPopoutHandlers } from './dashboard-popout' import { registerTerminalPreviewHandlers } from './terminal-preview' import { registerDeveloperPermissionHandlers } from './developer-permissions' import { registerComputerUsePermissionHandlers } from './computer-use-permissions' -import { setTrustedBrowserRendererWebContentsId, setAgentBrowserBridgeRef } from './browser' +import { + setTrustedBrowserRendererWebContentsId, + setAgentBrowserBridgeRef, + registerBrowserHandlers +} from './browser' import { registerSessionHandlers } from './session' import { registerSettingsHandlers } from './settings' import { registerDiagnosticsHandlers } from './diagnostics' @@ -45,7 +49,6 @@ import { registerLocalhostWorktreeLabelHandlers } from './localhost-worktree-lab import { registerAutomationHandlers } from './automations' import { registerKeybindingHandlers } from './keybindings' import { registerTelemetryHandlers } from './telemetry' -import { registerBrowserHandlers } from './browser' import { registerShellHandlers } from './shell' import { registerPetHandlers } from './pet' import { registerPluginHandlers } from './plugins' diff --git a/src/main/ipc/ssh-browse.ts b/src/main/ipc/ssh-browse.ts index 964c877a5..f42c64a80 100644 --- a/src/main/ipc/ssh-browse.ts +++ b/src/main/ipc/ssh-browse.ts @@ -1,5 +1,5 @@ import { ipcMain } from 'electron' -import type { SshConnectionManager } from '../ssh/ssh-connection' +import type { SshConnectionManager } from '../ssh/ssh-connection-manager' import type { SshExecOptions } from '../ssh/ssh-connection-utils' import { powerShellCommand, powerShellLiteral } from '../ssh/ssh-remote-powershell' diff --git a/src/main/ipc/ssh.test.ts b/src/main/ipc/ssh.test.ts index 3780b827e..f40370572 100644 --- a/src/main/ipc/ssh.test.ts +++ b/src/main/ipc/ssh.test.ts @@ -106,7 +106,7 @@ vi.mock('../ssh/ssh-connection-store', () => ({ } })) -vi.mock('../ssh/ssh-connection', () => ({ +vi.mock('../ssh/ssh-connection-manager', () => ({ SshConnectionManager: class MockSshConnectionManager { constructor(callbacks: unknown) { const manager = (mockNextConnectionManagers.shift() ?? diff --git a/src/main/ipc/ssh.ts b/src/main/ipc/ssh.ts index 287507985..846536fdd 100644 --- a/src/main/ipc/ssh.ts +++ b/src/main/ipc/ssh.ts @@ -3,7 +3,8 @@ import { ipcMain, powerMonitor, type BrowserWindow } from 'electron' import { appendFileSync } from 'node:fs' import type { Store } from '../persistence' import { SshConnectionStore } from '../ssh/ssh-connection-store' -import { SshConnectionManager, type SshConnectionCallbacks } from '../ssh/ssh-connection' +import type { SshConnectionCallbacks } from '../ssh/ssh-connection' +import { SshConnectionManager } from '../ssh/ssh-connection-manager' import type { SshChannelMultiplexer } from '../ssh/ssh-channel-multiplexer' import { SshRelaySession, type SshRelayAiVaultHostInfo } from '../ssh/ssh-relay-session' import { SshPortForwardManager } from '../ssh/ssh-port-forward' diff --git a/src/main/ipc/telemetry.ts b/src/main/ipc/telemetry.ts index 78f47fbbd..02f1b9e16 100644 --- a/src/main/ipc/telemetry.ts +++ b/src/main/ipc/telemetry.ts @@ -17,8 +17,7 @@ import { getOnboardingCohortAtEmit } from '../telemetry/onboarding-cohort-classi import { resolveConsent, type ConsentState } from '../telemetry/consent' import type { Store } from '../persistence' import { isCohortExtendedEvent, isOnboardingEvent } from '../../shared/telemetry-events' -import type { EventName, EventProps } from '../../shared/telemetry-events' -import type { OptInVia } from '../../shared/telemetry-events' +import type { EventName, EventProps, OptInVia } from '../../shared/telemetry-events' // Module-level store ref: handlers need a synchronous `settings.telemetry` read to derive `via` before any mutation. let storeRef: Store | null = null diff --git a/src/main/ipc/worktree-remote.ts b/src/main/ipc/worktree-remote.ts index b86e5c0ec..c10533acb 100644 --- a/src/main/ipc/worktree-remote.ts +++ b/src/main/ipc/worktree-remote.ts @@ -28,7 +28,7 @@ import { resolveDefaultBaseRefViaExec, resolveDefaultBaseRefWithLocalGit } from '../git/repo' -import { resolveLocalGitUsername } from '../git/git-username' +import { resolveLocalGitUsername, getSshGitUsername } from '../git/git-username' import { hasCommitObjectViaGitExec } from '../git/commit-object-ref' import { resolveWorktreeCreateBase } from '../worktree-create-base' import { resolveWorktreeAddBaseRef } from '../../shared/worktree-base-ref' @@ -38,8 +38,11 @@ import { validateGitPushTarget } from '../git/push-target-validation' import { assertGitPushTargetShape } from '../../shared/git-push-target-validation' import { gitExecFileAsync } from '../git/runner' import { parseGitHubOwnerRepo } from '../github/gh-utils' -import type { OrcaRuntimeService } from '../runtime/orca-runtime' -import type { RemoteFetchResult, RemoteTrackingBase } from '../runtime/orca-runtime' +import type { + OrcaRuntimeService, + RemoteFetchResult, + RemoteTrackingBase +} from '../runtime/orca-runtime' import { getProjectHostSetupWorktreeMeta } from '../../shared/project-host-setup-projection' import { buildPosixRunnerScript, @@ -58,7 +61,6 @@ import { getSshFilesystemProvider } from '../providers/ssh-filesystem-dispatch' import type { SshGitProvider } from '../providers/ssh-git-provider' import { TUI_AGENT_CONFIG, isTuiAgent } from '../../shared/tui-agent-config' import { isWindowsAbsolutePathLike } from '../../shared/cross-platform-path' -import { getSshGitUsername } from '../git/git-username' import { runWorktreeChangeInvalidators } from './worktree-change-invalidators' import { registerOptionalSshWorktreeCreateRoots, diff --git a/src/main/persistence.ts b/src/main/persistence.ts index 593ba050b..949167f56 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -94,7 +94,16 @@ import { type SshTarget } from '../shared/ssh-types' import { isFolderRepo } from '../shared/repo-kind' -import { getRepoExecutionHostId, parseExecutionHostId } from '../shared/execution-host' +import { + getRepoExecutionHostId, + parseExecutionHostId, + LOCAL_EXECUTION_HOST_ID, + normalizeExecutionHostOrder, + normalizeExecutionHostId, + normalizeVisibleExecutionHostIds, + toSshExecutionHostId, + type ExecutionHostId +} from '../shared/execution-host' import { getDefaultPersistedState, getDefaultNotificationSettings, @@ -116,14 +125,6 @@ import { normalizeStatusBarUsageMode } from '../shared/status-bar-usage-mode' import { isExistingPersistedProfile } from '../shared/project-order-manual-default-notice' import { resolveUsagePercentageDisplayChangeNoticeDismissed } from '../shared/usage-percentage-display-change-notice' import { normalizePRBotAuthorOverrides } from '../shared/pr-bot-author-overrides' -import { - LOCAL_EXECUTION_HOST_ID, - normalizeExecutionHostOrder, - normalizeExecutionHostId, - normalizeVisibleExecutionHostIds, - toSshExecutionHostId, - type ExecutionHostId -} from '../shared/execution-host' import { toRelaySshPtyId } from './providers/ssh-pty-id' import { migrateUiHostScopeSshTargetId, diff --git a/src/main/plugins/plugin-content-pack-registry.ts b/src/main/plugins/plugin-content-pack-registry.ts index c3901090c..40ad6978f 100644 --- a/src/main/plugins/plugin-content-pack-registry.ts +++ b/src/main/plugins/plugin-content-pack-registry.ts @@ -60,11 +60,10 @@ export class PluginContentPackRegistry { while (true) { const approveAtomically = (plugin: ValidDiscoveredPlugin): boolean => approvedKeys.has(plugin.pluginKey) && !excluded.has(plugin.pluginKey) - await Promise.all([ - this.languagePacks.reconcile(discovered, approveAtomically), - this.vmRecipes.reconcile(discovered, approveAtomically), - this.commands.reconcile(discovered, approveAtomically, keybindings) - ]) + const languagePacks = this.languagePacks.reconcile(discovered, approveAtomically) + const vmRecipes = this.vmRecipes.reconcile(discovered, approveAtomically) + this.commands.reconcile(discovered, approveAtomically, keybindings) + await Promise.all([languagePacks, vmRecipes]) let foundNewError = false for (const pluginKey of approvedKeys) { diff --git a/src/main/providers/local-pty-provider.ts b/src/main/providers/local-pty-provider.ts index e31110525..5e29ee31e 100644 --- a/src/main/providers/local-pty-provider.ts +++ b/src/main/providers/local-pty-provider.ts @@ -1,7 +1,6 @@ /* eslint-disable max-lines -- Why: splitting spawn() would scatter tightly coupled PTY lifecycle logic (scan → ready → write → exit) with no cleaner ownership seam. */ -import { basename, delimiter } from 'node:path' +import { basename, delimiter, win32 as pathWin32 } from 'node:path' import { randomUUID } from 'node:crypto' -import { win32 as pathWin32 } from 'node:path' import { resolveWindowsShellLaunchArgs } from './windows-shell-args' import { resolveEffectiveWindowsPowerShell, diff --git a/src/main/providers/ssh-git-provider.test.ts b/src/main/providers/ssh-git-provider.test.ts index 5dc32a48e..9974669fb 100644 --- a/src/main/providers/ssh-git-provider.test.ts +++ b/src/main/providers/ssh-git-provider.test.ts @@ -1009,7 +1009,7 @@ describe('SshGitProvider', () => { expect(mux.request).toHaveBeenCalledTimes(1) pendingDiff.resolve() - await expect(Promise.all(reads)).resolves.toEqual(Array(8).fill(diff)) + await expect(Promise.all(reads)).resolves.toEqual(Array.from({ length: 8 }, () => diff)) mux.request.mockReset() const branchDiffs = [diff] @@ -1026,7 +1026,9 @@ describe('SshGitProvider', () => { await waitForRequestCount(mux.request, 1) expect(mux.request).toHaveBeenCalledTimes(1) pendingBranchDiff.resolve() - await expect(Promise.all(branchReads)).resolves.toEqual(Array(8).fill(branchDiffs)) + await expect(Promise.all(branchReads)).resolves.toEqual( + Array.from({ length: 8 }, () => branchDiffs) + ) mux.request.mockReset() const pendingCommitDiff = deferredValue(diff) @@ -1043,7 +1045,7 @@ describe('SshGitProvider', () => { await waitForRequestCount(mux.request, 1) expect(mux.request).toHaveBeenCalledTimes(1) pendingCommitDiff.resolve() - await expect(Promise.all(commitReads)).resolves.toEqual(Array(8).fill(diff)) + await expect(Promise.all(commitReads)).resolves.toEqual(Array.from({ length: 8 }, () => diff)) }) it('retries diff RPCs after an in-flight rejection settles', async () => { diff --git a/src/main/rate-limits/claude-fetcher.ts b/src/main/rate-limits/claude-fetcher.ts index 206748e1b..79ce437b2 100644 --- a/src/main/rate-limits/claude-fetcher.ts +++ b/src/main/rate-limits/claude-fetcher.ts @@ -20,14 +20,14 @@ import { readActiveClaudeKeychainCredentials, readActiveClaudeKeychainCredentialsStrict, readManagedClaudeKeychainCredentials, - writeActiveClaudeKeychainCredentials + writeActiveClaudeKeychainCredentials, + writeManagedClaudeKeychainCredentials } from '../claude-accounts/keychain' import { readClaudeManagedAuthFile, resolveOwnedClaudeManagedAuthPath, writeClaudeManagedAuthFile } from '../claude-accounts/managed-auth-path' -import { writeManagedClaudeKeychainCredentials } from '../claude-accounts/keychain' import { isOauthTokenExpiring, refreshClaudeOauthCredentials diff --git a/src/main/runtime/orca-runtime-files.ts b/src/main/runtime/orca-runtime-files.ts index e37c71067..adbae1adf 100644 --- a/src/main/runtime/orca-runtime-files.ts +++ b/src/main/runtime/orca-runtime-files.ts @@ -1286,7 +1286,7 @@ export class RuntimeFileCommands { callback: (events: FsChangeEvent[]) => void, onTerminalError: (error: Error) => void = () => undefined, signal?: AbortSignal - ): Promise<() => void> { + ): Promise<() => Promise> { const target = await this.resolveFileExplorerPath(worktreeSelector, '') const open = async (): Promise<{ unsubscribe: () => Promise diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index f06a01d20..dac1b9f0f 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -184,7 +184,20 @@ import type { TuiAgent, WorkspaceCreateTelemetrySource, WorkspaceSessionState, - DirEntry + DirEntry, + GitHubIssueUpdate, + GitHubPullRequestStateUpdate, + GitHubPRFile, + GitHubPRReviewCommentInput, + GitLabIssueUpdate, + GitLabMRInlineCommentInput, + GitLabProjectRef, + GitLabWorkItem, + ListWorkItemsResult, + MRListState, + PRRefreshOutcome, + ClaudeRateLimitAccountsState, + CodexRateLimitAccountsState } from '../../shared/types' import { assertWorktreeUnlockedForRemoval } from '../../shared/worktree-removal' import { @@ -237,7 +250,65 @@ import type { } from '../../shared/linear-agent-access' import { HEADLESS_RUNTIME_WINDOW_ID, - type RuntimeDesktopWindowStatus + type RuntimeDesktopWindowStatus, + type RuntimeGraphStatus, + type RuntimeRepoSearchRefs, + type RuntimeTerminalRead, + type RuntimeTerminalRename, + type RuntimeTerminalAgentStatus, + type RuntimeTerminalSend, + type RuntimeTerminalCreate, + type RuntimeTerminalPresentation, + type RuntimeTerminalSplit, + type RuntimeTerminalFocus, + type RuntimeTerminalClose, + type RuntimeTerminalListResult, + type RuntimeTerminalOrphanAdoptionRequest, + type RuntimeTerminalOrphanAdoptionResult, + type RuntimeWorktreeTerminalSleepResult, + type RuntimeTerminalResolvePane, + type RuntimeTerminalState, + type RuntimeStatus, + type RuntimeSyncWindowGraphResult, + type RuntimeTerminalWait, + type RuntimeTerminalWaitBlockedReason, + type RuntimeTerminalWaitCondition, + type RuntimeWorktreePsSummary, + type RuntimeWorktreeAgentRow, + type RuntimeWorktreeStatus, + type RuntimeSpeechModelSummary, + type RuntimeSpeechSetupState, + type RuntimeTerminalShow, + type RuntimeTerminalSummary, + type RuntimeTerminalVisualGroupNode, + type RuntimeTerminalVisualLayout, + type RuntimeTerminalVisualLayoutNode, + type RuntimeTerminalVisualPaneNode, + type RuntimeTerminalVisualTab, + type RuntimeSyncedLeaf, + type RuntimeSyncedTab, + type RuntimeMarkdownReadTabResult, + type RuntimeMarkdownSaveTabResult, + type RuntimeMobileSessionCreateTerminalResult, + type RuntimeMobileSessionClientTab, + type RuntimeMobileSessionTabCloseResult, + type RuntimeMobileSessionMarkdownTab, + type RuntimeMobileSessionTabMove, + type RuntimeMobileSessionTabMoveResult, + type RuntimeMobileSessionTabGroup, + type RuntimeMobileSessionSnapshotTab, + type RuntimeMobileSessionTerminalTab, + type RuntimeMobileSessionBrowserTab, + type RuntimeMobileSessionTabsRemovedResult, + type RuntimeMobileSessionTabsResult, + type RuntimeMobileSessionTabsSnapshot, + type RuntimeSessionTabCloseReason, + type RuntimeBrowserDriverState, + type RuntimeTerminalDriverState, + type RuntimeSyncWindowGraph, + type RuntimeWorktreeListResult, + type BrowserTabInfo, + type BrowserScreencastResult } from '../../shared/runtime-types' import { LINEAR_SEARCH_MAX_LIMIT, @@ -377,66 +448,6 @@ import { scanWorkspacePortProbes } from '../ports/workspace-port-ownership' import { advertisedUrlWatcher } from '../ports/advertised-url-watcher' -import type { - RuntimeGraphStatus, - RuntimeRepoSearchRefs, - RuntimeTerminalRead, - RuntimeTerminalRename, - RuntimeTerminalAgentStatus, - RuntimeTerminalSend, - RuntimeTerminalCreate, - RuntimeTerminalPresentation, - RuntimeTerminalSplit, - RuntimeTerminalFocus, - RuntimeTerminalClose, - RuntimeTerminalListResult, - RuntimeTerminalOrphanAdoptionRequest, - RuntimeTerminalOrphanAdoptionResult, - RuntimeWorktreeTerminalSleepResult, - RuntimeTerminalResolvePane, - RuntimeTerminalState, - RuntimeStatus, - RuntimeSyncWindowGraphResult, - RuntimeTerminalWait, - RuntimeTerminalWaitBlockedReason, - RuntimeTerminalWaitCondition, - RuntimeWorktreePsSummary, - RuntimeWorktreeAgentRow, - RuntimeWorktreeStatus, - RuntimeSpeechModelSummary, - RuntimeSpeechSetupState, - RuntimeTerminalShow, - RuntimeTerminalSummary, - RuntimeTerminalVisualGroupNode, - RuntimeTerminalVisualLayout, - RuntimeTerminalVisualLayoutNode, - RuntimeTerminalVisualPaneNode, - RuntimeTerminalVisualTab, - RuntimeSyncedLeaf, - RuntimeSyncedTab, - RuntimeMarkdownReadTabResult, - RuntimeMarkdownSaveTabResult, - RuntimeMobileSessionCreateTerminalResult, - RuntimeMobileSessionClientTab, - RuntimeMobileSessionTabCloseResult, - RuntimeMobileSessionMarkdownTab, - RuntimeMobileSessionTabMove, - RuntimeMobileSessionTabMoveResult, - RuntimeMobileSessionTabGroup, - RuntimeMobileSessionSnapshotTab, - RuntimeMobileSessionTerminalTab, - RuntimeMobileSessionBrowserTab, - RuntimeMobileSessionTabsRemovedResult, - RuntimeMobileSessionTabsResult, - RuntimeMobileSessionTabsSnapshot, - RuntimeSessionTabCloseReason, - RuntimeBrowserDriverState, - RuntimeTerminalDriverState, - RuntimeSyncWindowGraph, - RuntimeWorktreeListResult, - BrowserTabInfo, - BrowserScreencastResult -} from '../../shared/runtime-types' import type { AutomationService } from '../automations/service' import { RuntimeBrowserCommands } from './orca-runtime-browser' import { RemoteRuntimeTerminalCreateIdempotency } from './remote-runtime-terminal-create-idempotency' @@ -474,7 +485,13 @@ import { deriveClientSessionTabSelection, projectClientSessionTabSelection } from './client-session-tab-selection' -import type { PtyProviderBufferSnapshot } from '../providers/types' +import type { + PtyProviderBufferSnapshot, + IFilesystemProvider, + IPtyProvider, + PtyProcessInfo, + PtyTransientFact +} from '../providers/types' import { ClaudeAgentTeamsService } from './claude-agent-teams-service' import type { AgentTeamsTmuxCompatRequest, @@ -527,9 +544,9 @@ import { addPRReviewCommentReply, listLabels, listAssignableUsers, - type MainWorkItem + type MainWorkItem, + type GitHubPRBranchLookupOptions } from '../github/client' -import type { GitHubPRBranchLookupOptions } from '../github/client' import { resolveGitHubPrStartPoint } from '../github/pr-start-point' import { fetchGitHubPullRequestHeadRef, @@ -579,19 +596,6 @@ import { type GitLabIssueListState } from '../gitlab/gitlab-preload-args' import { recordGitLabProjectRecent } from '../gitlab/gitlab-project-recents' -import type { - GitHubIssueUpdate, - GitHubPullRequestStateUpdate, - GitHubPRFile, - GitHubPRReviewCommentInput, - GitLabIssueUpdate, - GitLabMRInlineCommentInput, - GitLabProjectRef, - GitLabWorkItem, - ListWorkItemsResult, - MRListState, - PRRefreshOutcome -} from '../../shared/types' import { inspectSetupScriptImportCandidates } from '../../shared/setup-script-imports' import type { CreateHostedReviewInput, @@ -785,7 +789,7 @@ import { removeWorktree } from '../git/worktree' import type { AddWorktreeOptions, AddWorktreeResult } from '../git/worktree' -import { isENOENT } from '../ipc/filesystem-auth' +import { isENOENT, invalidateAuthorizedRootsCache } from '../ipc/filesystem-auth' import { createSetupRunnerScript, getDefaultTabCommandTrustContent, @@ -862,7 +866,6 @@ import { UNREGISTERED_MISSING_WORKTREE_MESSAGE } from '../worktree-removal-safety' import { prefetchWorktreeCreateBase } from '../worktree-create-base-prefetch' -import { invalidateAuthorizedRootsCache } from '../ipc/filesystem-auth' import { prepareLocalWorktreeRootForRepo } from '../worktree-root-preparation' import { closeLocalWatcherForWorktreePath, @@ -894,12 +897,6 @@ import { createMobileSessionTabsNotifyCoalescer, type MobileSessionTabsNotifyCoalescer } from './mobile-session-tabs-notify-coalescer' -import type { - IFilesystemProvider, - IPtyProvider, - PtyProcessInfo, - PtyTransientFact -} from '../providers/types' import { getSshFilesystemProvider } from '../providers/ssh-filesystem-dispatch' import { assertFolderWorkspacePathUsable, @@ -922,7 +919,6 @@ import type { } from '../codex-accounts/service' import type { CodexAccountSelectionTarget } from '../codex-accounts/runtime-selection' import type { RateLimitService } from '../rate-limits/service' -import type { ClaudeRateLimitAccountsState, CodexRateLimitAccountsState } from '../../shared/types' import { applyPRBotAuthorOverride } from '../../shared/pr-bot-author-overrides' import type { CodexRateLimitResetOutcome, RateLimitState } from '../../shared/rate-limit-types' import type { CodexResetCreditExpectedScope } from '../../shared/codex-reset-credit-scope' diff --git a/src/main/runtime/orchestration/coordinator.ts b/src/main/runtime/orchestration/coordinator.ts index 2af865a0d..7fcd5288f 100644 --- a/src/main/runtime/orchestration/coordinator.ts +++ b/src/main/runtime/orchestration/coordinator.ts @@ -367,7 +367,7 @@ export class Coordinator { terminals.push(created.handle) this.opts.onLog(`Created worker terminal ${created.handle}`) } catch (err) { - this.opts.onLog(`Failed to create terminal: ${err}`) + this.opts.onLog(`Failed to create terminal: ${String(err)}`) return } } @@ -383,7 +383,7 @@ export class Coordinator { try { await this.dispatchTask(task, targetHandle) } catch (err) { - this.opts.onLog(`Failed to dispatch task ${task.id}: ${err}`) + this.opts.onLog(`Failed to dispatch task ${task.id}: ${String(err)}`) } } } diff --git a/src/main/runtime/rpc/methods/agent-session.test.ts b/src/main/runtime/rpc/methods/agent-session.test.ts index b3aeb044d..f234dce15 100644 --- a/src/main/runtime/rpc/methods/agent-session.test.ts +++ b/src/main/runtime/rpc/methods/agent-session.test.ts @@ -6,8 +6,10 @@ import { RUNTIME_CAPABILITIES, RUNTIME_PROTOCOL_VERSION } from '../../../../shared/protocol-version' -import { AGENT_SESSION_RPC_ERROR_CODES } from '../../../../shared/agent-session-host-authority' -import { AGENT_SESSION_OPERATION_FUTURE_SKEW_MS } from '../../../../shared/agent-session-host-authority' +import { + AGENT_SESSION_RPC_ERROR_CODES, + AGENT_SESSION_OPERATION_FUTURE_SKEW_MS +} from '../../../../shared/agent-session-host-authority' import type { OrcaRuntimeService } from '../../orca-runtime' import type { RpcRequest, RpcResponse } from '../core' import { RpcDispatcher } from '../dispatcher' diff --git a/src/main/runtime/rpc/methods/ai-vault.ts b/src/main/runtime/rpc/methods/ai-vault.ts index cd6c9f00c..e494148ef 100644 --- a/src/main/runtime/rpc/methods/ai-vault.ts +++ b/src/main/runtime/rpc/methods/ai-vault.ts @@ -3,8 +3,7 @@ import { defineMethod, type RpcMethod } from '../core' import { OptionalBoolean } from '../schemas' import { restampAiVaultListResult } from '../../../ai-vault/session-list-results' import { AI_VAULT_AGENTS, AI_VAULT_SCOPE_PATHS_MAX_COUNT } from '../../../../shared/ai-vault-types' -import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' -import { parseExecutionHostId } from '../../../../shared/execution-host' +import { LOCAL_EXECUTION_HOST_ID, parseExecutionHostId } from '../../../../shared/execution-host' // Why: bound limit + scopePaths so a client cannot force an unbounded scan. // Each scopePath is a host-local match prefix (validated/capped, never used for diff --git a/src/main/runtime/rpc/methods/file-watch-stream-lifecycle.ts b/src/main/runtime/rpc/methods/file-watch-stream-lifecycle.ts index f612f27dc..daf01b27e 100644 --- a/src/main/runtime/rpc/methods/file-watch-stream-lifecycle.ts +++ b/src/main/runtime/rpc/methods/file-watch-stream-lifecycle.ts @@ -17,9 +17,9 @@ export async function runFileWatchStream(args: { let settled = false let setupFailed = false let watchReady = false - let unwatch: (() => void) | null = null + let unwatch: (() => Promise) | null = null let terminalError: Error | null = null - let setupPromise: Promise<() => void> | null = null + let setupPromise: Promise<() => Promise> | null = null let cleanupPromise: Promise | null = null let logicalCleanupStarted = false let endEmitted = false diff --git a/src/main/runtime/rpc/methods/native-chat.ts b/src/main/runtime/rpc/methods/native-chat.ts index 51758dbc8..1e94b9517 100644 --- a/src/main/runtime/rpc/methods/native-chat.ts +++ b/src/main/runtime/rpc/methods/native-chat.ts @@ -1,6 +1,9 @@ import { z } from 'zod' -import type { NativeChatBlock, NativeChatMessage } from '../../../../shared/native-chat-types' -import type { AgentType } from '../../../../shared/native-chat-types' +import type { + NativeChatBlock, + NativeChatMessage, + AgentType +} from '../../../../shared/native-chat-types' import { readNativeChatTranscriptTail, subscribeNativeChatTranscript diff --git a/src/main/runtime/runtime-rpc.test.ts b/src/main/runtime/runtime-rpc.test.ts index 87d984b6f..308c8ae20 100644 --- a/src/main/runtime/runtime-rpc.test.ts +++ b/src/main/runtime/runtime-rpc.test.ts @@ -50,7 +50,7 @@ async function sendRequest( let buffer = '' socket.setEncoding('utf8') socket.once('error', reject) - socket.on('data', (chunk) => { + socket.on('data', (chunk: string) => { buffer += chunk const newlineIndex = buffer.indexOf('\n') if (newlineIndex === -1) { @@ -4061,7 +4061,7 @@ describe('OrcaRuntimeRpcServer', () => { let buffer = '' socket.setEncoding('utf8') socket.once('error', reject) - socket.on('data', (chunk) => { + socket.on('data', (chunk: string) => { buffer += chunk const newlineIndex = buffer.indexOf('\n') if (newlineIndex === -1) { diff --git a/src/main/ssh/relay-protocol.ts b/src/main/ssh/relay-protocol.ts index c05b6c33b..0412c9799 100644 --- a/src/main/ssh/relay-protocol.ts +++ b/src/main/ssh/relay-protocol.ts @@ -301,7 +301,7 @@ export function parseJsonRpcMessage(payload: Buffer): JsonRpcMessage { const text = payload.toString('utf-8') const msg = JSON.parse(text) as JsonRpcMessage if (msg.jsonrpc !== '2.0') { - throw new Error(`Invalid JSON-RPC version: ${(msg as Record).jsonrpc}`) + throw new Error(`Invalid JSON-RPC version: ${String((msg as Record).jsonrpc)}`) } return msg } diff --git a/src/main/ssh/ssh-connection.test.ts b/src/main/ssh/ssh-connection.test.ts index 1ab923bee..3f4d4fbe0 100644 --- a/src/main/ssh/ssh-connection.test.ts +++ b/src/main/ssh/ssh-connection.test.ts @@ -154,10 +154,10 @@ vi.mock('./ssh-config-parser', () => ({ import { SshConnection, - SshConnectionManager, shouldUseSystemSshTransport, type SshConnectionCallbacks } from './ssh-connection' +import { SshConnectionManager } from './ssh-connection-manager' import { resolveWithSshG, type SshResolvedConfig } from './ssh-config-parser' import { downloadFileViaSystemSsh, diff --git a/src/main/ssh/ssh-connection.ts b/src/main/ssh/ssh-connection.ts index 899fa5b35..224f888ea 100644 --- a/src/main/ssh/ssh-connection.ts +++ b/src/main/ssh/ssh-connection.ts @@ -1371,5 +1371,3 @@ export function shouldUseSystemSshTransport( resolved?.proxyJump != null ) } - -export { SshConnectionManager } from './ssh-connection-manager' diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index d60947df6..245b0d3a1 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -32,7 +32,15 @@ import type { } from '../shared/terminal-render-desync-evidence' import type { MobileRelayStatus } from '../shared/mobile-relay-status' import type { MobilePairingConnectionMode } from '../shared/mobile-pairing-connection-mode' -import type { SshMutationExpectation } from '../shared/ssh-types' +import type { + SshMutationExpectation, + SshConnectionState, + SshConfigImportResult, + SshTargetAddResult, + SshTarget, + PortForwardEntry, + EnrichedDetectedPort +} from '../shared/ssh-types' import type { CreateLocalOrcaProfileArgs, CreateLocalOrcaProfileResult, @@ -260,10 +268,8 @@ import type { import type { SetupScriptImportCandidate } from '../shared/setup-script-imports' import type { GitHistoryOptions, GitHistoryResult } from '../shared/git-history' import type { PublicKnownRuntimeEnvironment } from '../shared/runtime-environments' -import type { - EphemeralVmRecipeDoctorResult, - EphemeralVmRecipeResultWarning -} from '../shared/ephemeral-vm-recipes' +import type { EphemeralVmRecipeDoctorResult } from '../shared/ephemeral-vm-recipes' +import type { EphemeralVmRecipeResultWarning } from '../shared/ephemeral-vm-recipe-diagnostics' import type { EphemeralVmRuntimeRecord } from '../shared/ephemeral-vm-runtimes' import type { RuntimeAccessGrant } from '../shared/runtime-access-grants' import type { RuntimeRpcResponse } from '../shared/runtime-rpc-envelope' @@ -431,14 +437,6 @@ import type { WorkspacePortScanResult } from '../shared/workspace-ports' import type { GhAuthDiagnostic } from '../shared/github-auth-types' -import type { - SshConnectionState, - SshConfigImportResult, - SshTargetAddResult, - SshTarget, - PortForwardEntry, - EnrichedDetectedPort -} from '../shared/ssh-types' import type { CodexUsageBreakdownKind, CodexUsageBreakdownRow, diff --git a/src/preload/index.ts b/src/preload/index.ts index bb0a17220..3023f7382 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -21,7 +21,15 @@ import type { } from '../shared/agent-session-resume' import type { MobileRelayStatus } from '../shared/mobile-relay-status' import type { MobilePairingConnectionMode } from '../shared/mobile-pairing-connection-mode' -import type { SshMutationExpectation } from '../shared/ssh-types' +import type { + SshMutationExpectation, + SshConnectionState, + SshConfigImportResult, + SshTargetAddResult, + SshTarget, + PortForwardEntry, + EnrichedDetectedPort +} from '../shared/ssh-types' import type { PluginPanelActionOutcome, PluginPanelEntry @@ -158,14 +166,6 @@ import { richMarkdownContextMenuCommandChannel, type RichMarkdownContextMenuCommandPayload } from '../shared/rich-markdown-context-menu' -import type { - SshConnectionState, - SshConfigImportResult, - SshTargetAddResult, - SshTarget, - PortForwardEntry, - EnrichedDetectedPort -} from '../shared/ssh-types' import type { AgentStatusClearIpcPayload, AgentStatusIpcPayload, @@ -182,7 +182,18 @@ import type { SpeechTranscriptEvent } from '../shared/speech-types' import type { TelemetryConsentState } from '../shared/telemetry-consent-types' -import type { PreflightRuntimeContext, RefreshAgentsResult } from './api-types' +import type { + PreflightRuntimeContext, + RefreshAgentsResult, + NativeChatAppendedPayload, + NativeChatReadSessionResult, + NativeChatSubscriptionFrame, + PluginHostInstallResult, + PluginHostInstallSource, + PluginHostListEntry, + PluginHostLogLine, + PreloadApi +} from './api-types' import type { AgentKind, LaunchSource, RequestKind } from '../shared/telemetry-events' import type { AppStarSource } from '../shared/gh-star-source' import type { ExecutionHostId } from '../shared/execution-host' @@ -205,11 +216,6 @@ import type { KeybindingActionId, KeybindingFileSnapshot } from '../shared/keybi import type { AiVaultListArgs, AiVaultSubagentListArgs } from '../shared/ai-vault-types' import type { AiVaultPrepareSessionResumeArgs } from '../shared/ai-vault-resume-preparation' import type { AgentType } from '../shared/native-chat-types' -import type { - NativeChatAppendedPayload, - NativeChatReadSessionResult, - NativeChatSubscriptionFrame -} from './api-types' import { ORCA_APP_RESTART_ABORTED_EVENT, ORCA_APP_RESTART_STARTED_EVENT, @@ -252,13 +258,6 @@ import type { } from '../shared/crash-reporting' import type { RendererHeapStatistics } from '../shared/renderer-heap-statistics' import { readRendererHeapStatistics } from './renderer-heap-statistics-reader' -import type { - PluginHostInstallResult, - PluginHostInstallSource, - PluginHostListEntry, - PluginHostLogLine, - PreloadApi -} from './api-types' import { createUpdaterQuitAbortRelay, prepareRendererForAppRestart diff --git a/src/relay/protocol.ts b/src/relay/protocol.ts index 4df0f7bd9..617ab84e6 100644 --- a/src/relay/protocol.ts +++ b/src/relay/protocol.ts @@ -286,7 +286,7 @@ export function parseJsonRpcMessage(payload: Buffer): JsonRpcMessage { const text = payload.toString('utf-8') const msg = JSON.parse(text) as JsonRpcMessage if (msg.jsonrpc !== '2.0') { - throw new Error(`Invalid JSON-RPC version: ${(msg as Record).jsonrpc}`) + throw new Error(`Invalid JSON-RPC version: ${String((msg as Record).jsonrpc)}`) } return msg } diff --git a/src/relay/relay.ts b/src/relay/relay.ts index 267b9ee8b..82110b58d 100644 --- a/src/relay/relay.ts +++ b/src/relay/relay.ts @@ -342,7 +342,7 @@ async function main(): Promise { }) process.on('unhandledRejection', (reason) => { - relayLogLine(`[relay] Unhandled rejection: ${reason}`) + relayLogLine(`[relay] Unhandled rejection: ${String(reason)}`) }) // Why: guards writes after the stdin/SSH channel drops so keepalive/pty.data frames don't hit a dead pipe (EPIPE). diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx index ddb11435c..35c7e2184 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import React from 'react' -import { act } from 'react' +import React, { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import NewWorkspaceComposerCard from './NewWorkspaceComposerCard' diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.tsx index 0fdcc3459..4e89e8b0e 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.tsx @@ -33,6 +33,10 @@ import { SettingsSwitch } from '@/components/settings/SettingsFormControls' import type RepoCombobox from '@/components/repo/RepoCombobox' import AgentCombobox from '@/components/agent/AgentCombobox' import { getAgentCatalog } from '@/lib/agent-catalog' +import { + DEFAULT_DISABLED_TUI_AGENTS, + filterEnabledTuiAgents +} from '../../../shared/tui-agent-selection' import { useAppStore } from '@/store' import { cn } from '@/lib/utils' import { WORKSPACE_FILE_PATH_MIME } from '@/lib/workspace-file-drag' @@ -44,7 +48,6 @@ import { } from '@/lib/text-control-paste' import { getScreenSubmitModifierLabel } from '@/lib/screen-submit-shortcut' import { useContextualTour } from '@/components/contextual-tours/use-contextual-tour' -import { filterEnabledTuiAgents } from '../../../shared/tui-agent-selection' import type { GitHubWorkItem, GitLabWorkItem, @@ -955,7 +958,9 @@ export default function NewWorkspaceComposerCard({ const openModal = useAppStore((s) => s.openModal) const activeModal = useAppStore((s) => s.activeModal) const defaultTuiAgent = useAppStore((s) => s.settings?.defaultTuiAgent ?? null) - const disabledTuiAgents = useAppStore((s) => s.settings?.disabledTuiAgents ?? []) + const disabledTuiAgents = useAppStore( + (s) => s.settings?.disabledTuiAgents ?? DEFAULT_DISABLED_TUI_AGENTS + ) const updateSettings = useAppStore((s) => s.updateSettings) const nameInputFocusFrameRef = React.useRef(null) const branchNameInputId = React.useId() diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index d14db9fcb..4a1d74e63 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -44,7 +44,8 @@ import { getLocalPreflightContext, localPreflightContextKey } from '@/lib/local- import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' import { getSettingsFocusedExecutionHostId, - parseExecutionHostId + parseExecutionHostId, + getRepoExecutionHostId } from '../../../shared/execution-host' import { Button } from '@/components/ui/button' import { ButtonGroup } from '@/components/ui/button-group' @@ -179,7 +180,6 @@ import { readLinearBoardIssueDragData, writeLinearBoardIssueDragData } from '@/lib/linear-board-drag-payload' -import { getRepoExecutionHostId } from '../../../shared/execution-host' import { projectHostSetupProjectionFromRepos } from '../../../shared/project-host-setup-projection' import { TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY } from '../../../shared/protocol-version' import { diff --git a/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.test.tsx b/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.test.tsx index 4865021f0..ac551fbc1 100644 --- a/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.test.tsx +++ b/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.test.tsx @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import React, { type ReactNode } from 'react' -import { act } from 'react' +import React, { type ReactNode, act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { AgentSessionContinuationRequest } from '@/lib/agent-session-continuation' diff --git a/src/renderer/src/components/dictation/use-hold-dictation-gesture.test.tsx b/src/renderer/src/components/dictation/use-hold-dictation-gesture.test.tsx index ee1520545..ef83b0762 100644 --- a/src/renderer/src/components/dictation/use-hold-dictation-gesture.test.tsx +++ b/src/renderer/src/components/dictation/use-hold-dictation-gesture.test.tsx @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import { createRef, type MutableRefObject } from 'react' -import { act } from 'react' +import { createRef, type MutableRefObject, act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { DictationState } from '../../../../shared/speech-types' diff --git a/src/renderer/src/components/editor/ChangesModeView.test.tsx b/src/renderer/src/components/editor/ChangesModeView.test.tsx index d8b41e4b3..ae8d13f5a 100644 --- a/src/renderer/src/components/editor/ChangesModeView.test.tsx +++ b/src/renderer/src/components/editor/ChangesModeView.test.tsx @@ -1,6 +1,5 @@ // @vitest-environment happy-dom -import { Suspense } from 'react' -import { act } from 'react' +import { act, Suspense } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, describe, expect, it, vi } from 'vitest' import type { OpenFile } from '@/store/slices/editor' diff --git a/src/renderer/src/components/editor/MonacoEditor.tsx b/src/renderer/src/components/editor/MonacoEditor.tsx index 417aaccf6..15718697a 100644 --- a/src/renderer/src/components/editor/MonacoEditor.tsx +++ b/src/renderer/src/components/editor/MonacoEditor.tsx @@ -4,7 +4,7 @@ import React, { useRef, useCallback, useEffect, useLayoutEffect, useMemo, useSta import Editor, { type OnMount } from '@monaco-editor/react' import type { editor } from 'monaco-editor' import { toast } from 'sonner' -import type { MarkdownDocument } from '../../../../shared/types' +import type { MarkdownDocument, DiffComment } from '../../../../shared/types' import { useAppStore } from '@/store' import { scrollTopCache, cursorPositionCache, setWithLRU } from '@/lib/scroll-cache' import '@/lib/monaco-setup' @@ -36,7 +36,6 @@ import { } from './monaco-markdown-doc-link-decorations' import { buildGitConflictDecorations, hasGitConflictMarkers } from './monaco-conflict-decorations' import { selectWorktreeDiffComments } from '@/store/worktree-diff-comments-selector' -import type { DiffComment } from '../../../../shared/types' import { isMarkdownComment } from '@/lib/diff-comment-compat' import { formatMarkdownReviewNotes, type MarkdownReviewNote } from '@/lib/markdown-review-notes' import { useDiffCommentDecorator } from '../diff-comments/useDiffCommentDecorator' diff --git a/src/renderer/src/components/editor/ReviewNotesSendMenuContent.tsx b/src/renderer/src/components/editor/ReviewNotesSendMenuContent.tsx index 39220edb9..25b55305c 100644 --- a/src/renderer/src/components/editor/ReviewNotesSendMenuContent.tsx +++ b/src/renderer/src/components/editor/ReviewNotesSendMenuContent.tsx @@ -20,8 +20,11 @@ import { deriveNotesSendAgentTargets, type NotesSendAgentTarget } from '@/lib/notes-send-agent-targets' -import { agentKindForAgentType, formatAgentTypeLabel } from '@/lib/agent-status' -import { agentTypeToIconAgent } from '@/lib/agent-status' +import { + agentKindForAgentType, + formatAgentTypeLabel, + agentTypeToIconAgent +} from '@/lib/agent-status' import { track } from '@/lib/telemetry' import { useNow } from '@/components/dashboard/useNow' import type { DashboardAgentRow as DashboardAgentRowData } from '@/components/dashboard/useDashboardData' diff --git a/src/renderer/src/components/editor/markdown-round-trip.test.ts b/src/renderer/src/components/editor/markdown-round-trip.test.ts index c1ab84354..fabdc6b4d 100644 --- a/src/renderer/src/components/editor/markdown-round-trip.test.ts +++ b/src/renderer/src/components/editor/markdown-round-trip.test.ts @@ -32,9 +32,9 @@ function markdownAfterTextReplace(content: string, search: string, replacement: }) try { - let from: number | null = null + let from = -1 editor.state.doc.descendants((node, pos) => { - if (from !== null || !node.isText || !node.text) { + if (from !== -1 || !node.isText || !node.text) { return } const index = node.text.indexOf(search) @@ -42,7 +42,7 @@ function markdownAfterTextReplace(content: string, search: string, replacement: from = pos + index } }) - if (from === null) { + if (from === -1) { throw new Error(`Missing text: ${search}`) } editor.view.dispatch(editor.state.tr.insertText(replacement, from, from + search.length)) diff --git a/src/renderer/src/components/editor/useEditorPanelContentState.ts b/src/renderer/src/components/editor/useEditorPanelContentState.ts index 26cc45e7c..d7c62b6e6 100644 --- a/src/renderer/src/components/editor/useEditorPanelContentState.ts +++ b/src/renderer/src/components/editor/useEditorPanelContentState.ts @@ -329,7 +329,7 @@ export function useEditorPanelContentState({ [file.id]: { kind: 'text', originalContent: '', - modifiedContent: `Error loading diff: ${err}`, + modifiedContent: `Error loading diff: ${String(err)}`, originalIsBinary: false, modifiedIsBinary: false } diff --git a/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts b/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts index d18c744f4..7f98c9052 100644 --- a/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts +++ b/src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts @@ -1,7 +1,9 @@ import { useCallback, useEffect, useRef } from 'react' import { FEATURE_WALL_MAX_DWELL_MS } from '../../../../shared/feature-wall-telemetry' -import type { FeatureWallExitAction } from '../../../../shared/feature-wall-tour-depth' -import type { FeatureWallTourDepthSummary } from '../../../../shared/feature-wall-tour-depth' +import type { + FeatureWallExitAction, + FeatureWallTourDepthSummary +} from '../../../../shared/feature-wall-tour-depth' import type { EventProps, FeatureWallOpenSourceTelemetry diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx index a06993327..d2ff07bfd 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx @@ -10,7 +10,10 @@ import { buildAgentStartupPlan } from '@/lib/tui-agent-startup' import { tuiAgentToAgentKind } from '@/lib/telemetry' import { useAppStore } from '@/store' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' -import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection' +import { + DEFAULT_DISABLED_TUI_AGENTS, + isTuiAgentEnabled +} from '../../../../shared/tui-agent-selection' import { resolveTuiAgentLaunchArgs, resolveTuiAgentLaunchEnv @@ -49,7 +52,9 @@ export function FloatingTerminalWindowControls({ const maximizeShortcutLabel = useOptionalShortcutLabel('floatingWorkspace.maximize') const minimizeShortcutLabel = useOptionalShortcutLabel('floatingWorkspace.minimize') - const disabledTuiAgents = useAppStore((s) => s.settings?.disabledTuiAgents ?? []) + const disabledTuiAgents = useAppStore( + (s) => s.settings?.disabledTuiAgents ?? DEFAULT_DISABLED_TUI_AGENTS + ) const defaultAgent = defaultTuiAgent && defaultTuiAgent !== 'blank' && diff --git a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx index af94be760..a8b20151a 100644 --- a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx +++ b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx @@ -7,9 +7,8 @@ import { Input } from '@/components/ui/input' import CommentMarkdown from '@/components/sidebar/CommentMarkdown' import { useAppStore } from '@/store' import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' -import type { GitHubWorkItemDetails } from '../../../../../shared/types' +import type { GitHubWorkItemDetails, GlobalSettings } from '../../../../../shared/types' import type { GitHubItemDialogProjectOrigin } from '@/components/GitHubItemDialog' -import type { GlobalSettings } from '../../../../../shared/types' import { LabelsEditor } from './LabelsEditor' import { AssigneesEditor } from './AssigneesEditor' import { CommentsList, NewCommentForm } from './Comments' diff --git a/src/renderer/src/components/mobile/MobilePageContent.tsx b/src/renderer/src/components/mobile/MobilePageContent.tsx index 432e4e5f6..d2e7d21fa 100644 --- a/src/renderer/src/components/mobile/MobilePageContent.tsx +++ b/src/renderer/src/components/mobile/MobilePageContent.tsx @@ -1,7 +1,13 @@ import { translate } from '@/i18n/i18n' import type { MobileNetworkInterface } from '../settings/mobile-network-interface-selection' -import { HeroFlow, HeroIntro, HeroPaired, type PairedDevice, type Platform } from './MobileHero' -import type { StepIndex } from './MobileHero' +import { + HeroFlow, + HeroIntro, + HeroPaired, + type PairedDevice, + type Platform, + type StepIndex +} from './MobileHero' import { getInstallCopy, type IosChannel } from './mobile-platform-copy' import type { MobilePageStage } from './mobile-page-stage' import { MobilePageToolbar } from './MobilePageToolbar' diff --git a/src/renderer/src/components/native-chat/use-native-chat-composer-attachments.test.tsx b/src/renderer/src/components/native-chat/use-native-chat-composer-attachments.test.tsx index eb92f1746..82d788e94 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-composer-attachments.test.tsx +++ b/src/renderer/src/components/native-chat/use-native-chat-composer-attachments.test.tsx @@ -1,8 +1,7 @@ // @vitest-environment happy-dom import { afterEach, describe, expect, it, vi } from 'vitest' -import { act } from 'react' +import { act, createElement, useRef, useState } from 'react' import { createRoot, type Root } from 'react-dom/client' -import { createElement, useRef, useState } from 'react' import { clearNativeChatAttachmentCacheForTests, readNativeChatAttachmentCache, diff --git a/src/renderer/src/components/native-chat/use-native-chat-composer-paste.test.tsx b/src/renderer/src/components/native-chat/use-native-chat-composer-paste.test.tsx index 1f24874cd..158ab0bfa 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-composer-paste.test.tsx +++ b/src/renderer/src/components/native-chat/use-native-chat-composer-paste.test.tsx @@ -1,8 +1,7 @@ // @vitest-environment happy-dom import { afterEach, describe, expect, it, vi } from 'vitest' -import { act } from 'react' +import { act, createElement } from 'react' import { createRoot, type Root } from 'react-dom/client' -import { createElement } from 'react' import type { NativeChatAttachmentOwner } from './native-chat-attachment-upload' const mocks = vi.hoisted(() => ({ diff --git a/src/renderer/src/components/native-chat/use-native-chat-external-attachments.test.tsx b/src/renderer/src/components/native-chat/use-native-chat-external-attachments.test.tsx index fc72a23ad..cc2dc4c92 100644 --- a/src/renderer/src/components/native-chat/use-native-chat-external-attachments.test.tsx +++ b/src/renderer/src/components/native-chat/use-native-chat-external-attachments.test.tsx @@ -1,8 +1,7 @@ // @vitest-environment happy-dom import { afterEach, describe, expect, it, vi } from 'vitest' -import { act } from 'react' +import { act, createElement } from 'react' import { createRoot, type Root } from 'react-dom/client' -import { createElement } from 'react' const mocks = vi.hoisted(() => ({ resolveNativeChatAttachmentOwner: vi.fn(), diff --git a/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.test.ts b/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.test.ts index a0fb9c00f..893e1c38f 100644 --- a/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.test.ts +++ b/src/renderer/src/components/onboarding/use-onboarding-flow-persistence.test.ts @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import { createElement, useEffect } from 'react' -import { act } from 'react' +import { createElement, useEffect, act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { getDefaultOnboardingState } from '../../../../shared/constants' diff --git a/src/renderer/src/components/right-sidebar/SourceControlAgentActionDialog.test.tsx b/src/renderer/src/components/right-sidebar/SourceControlAgentActionDialog.test.tsx index d0b4f798a..2b87618bd 100644 --- a/src/renderer/src/components/right-sidebar/SourceControlAgentActionDialog.test.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControlAgentActionDialog.test.tsx @@ -1,8 +1,7 @@ // @vitest-environment happy-dom import path from 'node:path' -import React, { type ReactNode, useState } from 'react' -import { act } from 'react' +import React, { type ReactNode, useState, act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { getDefaultSettings } from '../../../../shared/constants' diff --git a/src/renderer/src/components/right-sidebar/checks-panel-content.tsx b/src/renderer/src/components/right-sidebar/checks-panel-content.tsx index 0dd54d7b7..ab2716d15 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel-content.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel-content.tsx @@ -25,9 +25,9 @@ import { Pencil, SlidersHorizontal, Trash, - X + X, + ExternalLink } from 'lucide-react' -import { ExternalLink } from 'lucide-react' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' diff --git a/src/renderer/src/components/right-sidebar/index.tsx b/src/renderer/src/components/right-sidebar/index.tsx index cd64ea47d..f9587ab8a 100644 --- a/src/renderer/src/components/right-sidebar/index.tsx +++ b/src/renderer/src/components/right-sidebar/index.tsx @@ -2,11 +2,10 @@ import React, { useEffect, useMemo, useRef, useState } from 'react' import { Plug, Files, GitBranch, ListChecks, PanelRight, Workflow } from 'lucide-react' import { useAppStore } from '@/store' -import type { ActiveRightSidebarTab } from '@/store/slices/editor' +import type { ActiveRightSidebarTab, ActivityBarPosition } from '@/store/slices/editor' import { useRepoById } from '@/store/selectors' import { cn } from '@/lib/utils' import { useSidebarResize } from '@/hooks/useSidebarResize' -import type { ActivityBarPosition } from '@/store/slices/editor' import { isFolderRepo } from '../../../../shared/repo-kind' import { parseWorkspaceKey } from '../../../../shared/workspace-scope' import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip' diff --git a/src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.ts b/src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.ts index 2475a7d29..4bae17461 100644 --- a/src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.ts +++ b/src/renderer/src/components/right-sidebar/source-control-create-review-blocked-action.ts @@ -68,6 +68,7 @@ export function resolveBlockedCreateReviewNoticeMessage( case 'unsupported_provider': // Why: base_not_on_remote is a create-time hard failure surfaced as an error // result, not an inline-actionable eligibility state, so it is non-clickable. + // falls through case 'base_not_on_remote': case null: return null diff --git a/src/renderer/src/components/right-sidebar/source-control-entry-context-menu.tsx b/src/renderer/src/components/right-sidebar/source-control-entry-context-menu.tsx index 6d3076d5c..0c0417a79 100644 --- a/src/renderer/src/components/right-sidebar/source-control-entry-context-menu.tsx +++ b/src/renderer/src/components/right-sidebar/source-control-entry-context-menu.tsx @@ -14,6 +14,7 @@ import { useAppStore } from '@/store' import { OpenInApplicationIcon } from '@/lib/open-in-app-catalog' import { translate } from '@/i18n/i18n' import { getLocalFileManagerLabel } from '@/lib/local-file-manager-label' +import { NO_OPEN_IN_APPLICATIONS } from '@/lib/open-in-application-selection' import { getOpenInEntryAvailability, getWorktreeOpenInEntries, @@ -42,7 +43,9 @@ export function SourceControlEntryContextMenu({ onOpenChange, children }: SourceControlEntryContextMenuProps): React.JSX.Element { - const openInApplications = useAppStore((s) => s.settings?.openInApplications ?? []) + const openInApplications = useAppStore( + (s) => s.settings?.openInApplications ?? NO_OPEN_IN_APPLICATIONS + ) const settings = useAppStore((s) => s.settings) const fileManagerLabel = getLocalFileManagerLabel() const openInEntries = React.useMemo( diff --git a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts index 88bac1437..9558eb9a3 100644 --- a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts +++ b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts @@ -3,8 +3,7 @@ import { toast } from 'sonner' import { useConfirmationDialog } from '@/components/confirmation-dialog' import type { GitHubPRAutoMergeAction } from '@/components/github-pr-merge-state' import type { HostedReviewInfo } from '../../../../shared/hosted-review' -import type { PRInfo, Repo } from '../../../../shared/types' -import type { GitHubPRMergeMethod } from '../../../../shared/types' +import type { PRInfo, Repo, GitHubPRMergeMethod } from '../../../../shared/types' import { mergeGitHubHostedReview, setGitHubHostedReviewAutoMerge, diff --git a/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.test.ts b/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.test.ts index 8017aea44..be2f142ad 100644 --- a/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.test.ts +++ b/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.test.ts @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import React from 'react' -import { act } from 'react' +import React, { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { describe, expect, it, vi } from 'vitest' import type { HostedReviewCreationEligibility } from '../../../../shared/hosted-review' diff --git a/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.ts b/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.ts index 815a18abb..1bc838642 100644 --- a/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.ts +++ b/src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.ts @@ -13,10 +13,9 @@ import { getRuntimeRepoBaseRefDefault, searchRuntimeRepoBaseRefDetails } from '@/runtime/runtime-repo-client' -import type { Repo } from '../../../../shared/types' +import type { Repo, BaseRefSearchResult } from '../../../../shared/types' import type { HostedReviewCreationEligibility } from '../../../../shared/hosted-review' import { normalizeHostedReviewBaseRef } from '../../../../shared/hosted-review-refs' -import type { BaseRefSearchResult } from '../../../../shared/types' import { DEFAULT_SOURCE_CONTROL_AI_PR_CREATION_DEFAULTS, resolveSourceControlAiForOperation diff --git a/src/renderer/src/components/settings/MobileEmulatorSettingsPane.tsx b/src/renderer/src/components/settings/MobileEmulatorSettingsPane.tsx index 69d420bbd..e4f5adaa4 100644 --- a/src/renderer/src/components/settings/MobileEmulatorSettingsPane.tsx +++ b/src/renderer/src/components/settings/MobileEmulatorSettingsPane.tsx @@ -35,7 +35,7 @@ type EmulatorAvailability = { type MobileEmulatorSettingsPaneProps = { settings: GlobalSettings - updateSettings: (updates: Partial) => void + updateSettings: (updates: Partial) => Promise } const AUTOMATIC_DEVICE_VALUE = '__orca_automatic_emulator_device__' diff --git a/src/renderer/src/components/settings/RepositoryHooksSection.test.ts b/src/renderer/src/components/settings/RepositoryHooksSection.test.ts index c1f4390e5..6ca17d9fc 100644 --- a/src/renderer/src/components/settings/RepositoryHooksSection.test.ts +++ b/src/renderer/src/components/settings/RepositoryHooksSection.test.ts @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import React from 'react' -import { act } from 'react' +import React, { act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, describe, expect, it, vi } from 'vitest' import type { Repo } from '../../../../shared/types' diff --git a/src/renderer/src/components/settings/TerminalAppearanceSection.tsx b/src/renderer/src/components/settings/TerminalAppearanceSection.tsx index 8bfacae4a..d0ee90eb0 100644 --- a/src/renderer/src/components/settings/TerminalAppearanceSection.tsx +++ b/src/renderer/src/components/settings/TerminalAppearanceSection.tsx @@ -21,9 +21,8 @@ import { getTerminalWindowSearchEntries } from './terminal-search' import { Button } from '../ui/button' -import { SettingsRow, SettingsSubsectionHeader } from './SettingsFormControls' +import { SettingsRow, SettingsSubsectionHeader, FontAutocomplete } from './SettingsFormControls' import { SearchableSetting } from './SearchableSetting' -import { FontAutocomplete } from './SettingsFormControls' import { TerminalFontSizeSetting } from './TerminalFontSizeSetting' import { TerminalAdvancedTypographyControls } from './TerminalAdvancedTypographyControls' import { TerminalThemeCatalogSection } from './TerminalThemeSections' diff --git a/src/renderer/src/components/settings/VoiceSpeechModelSection.tsx b/src/renderer/src/components/settings/VoiceSpeechModelSection.tsx index acd8409e7..31c1595a9 100644 --- a/src/renderer/src/components/settings/VoiceSpeechModelSection.tsx +++ b/src/renderer/src/components/settings/VoiceSpeechModelSection.tsx @@ -1,7 +1,10 @@ import { useState } from 'react' import { toast } from 'sonner' -import type { VoiceSettings } from '../../../../shared/speech-types' -import type { SpeechModelManifest, SpeechModelState } from '../../../../shared/speech-types' +import type { + VoiceSettings, + SpeechModelManifest, + SpeechModelState +} from '../../../../shared/speech-types' import { Button } from '../ui/button' import { Label } from '../ui/label' import { diff --git a/src/renderer/src/components/settings/repository-source-control-ai-labels.ts b/src/renderer/src/components/settings/repository-source-control-ai-labels.ts index 7255b3e8b..64f0a1110 100644 --- a/src/renderer/src/components/settings/repository-source-control-ai-labels.ts +++ b/src/renderer/src/components/settings/repository-source-control-ai-labels.ts @@ -5,10 +5,12 @@ import { type SourceControlActionId, type SourceControlActionRecipe } from '../../../../shared/source-control-ai-actions' -import type { RepoSourceControlAiOverrides } from '../../../../shared/source-control-ai-types' +import type { + RepoSourceControlAiOverrides, + SourceControlAiSettings +} from '../../../../shared/source-control-ai-types' import type { CustomAgentId } from '../../../../shared/commit-message-agent-spec' import { isCustomAgentId } from '../../../../shared/commit-message-agent-spec' -import type { SourceControlAiSettings } from '../../../../shared/source-control-ai-types' export const ACTION_MODE_INHERIT = 'inherit' export const ACTION_MODE_OVERRIDE = 'override' diff --git a/src/renderer/src/components/settings/settings-search-keywords.ts b/src/renderer/src/components/settings/settings-search-keywords.ts index d9ee30033..4a14a6862 100644 --- a/src/renderer/src/components/settings/settings-search-keywords.ts +++ b/src/renderer/src/components/settings/settings-search-keywords.ts @@ -1,5 +1,4 @@ -import { i18n } from '@/i18n/i18n' -import { translate } from '@/i18n/i18n' +import { i18n, translate } from '@/i18n/i18n' export type SearchKeywordSpec = { key: string diff --git a/src/renderer/src/components/sidebar/SidebarNav.test.tsx b/src/renderer/src/components/sidebar/SidebarNav.test.tsx index 42e7ee20f..93fee95eb 100644 --- a/src/renderer/src/components/sidebar/SidebarNav.test.tsx +++ b/src/renderer/src/components/sidebar/SidebarNav.test.tsx @@ -80,7 +80,7 @@ vi.mock('@/components/ui/context-menu', () => ({ ) })) -import { +import SidebarNav, { getSetupGuideSidebarEntryReady, shouldShowAgentDashboardButton, shouldShowAgentsButton, @@ -88,7 +88,6 @@ import { shouldShowMobileButton, shouldShowSetupGuideEntry } from './SidebarNav' -import SidebarNav from './SidebarNav' function gitRepo(): Repo { return { diff --git a/src/renderer/src/components/sidebar/WorktreeCard.compact-ports-hover-independence.test.tsx b/src/renderer/src/components/sidebar/WorktreeCard.compact-ports-hover-independence.test.tsx index ca6064518..3887c52c7 100644 --- a/src/renderer/src/components/sidebar/WorktreeCard.compact-ports-hover-independence.test.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCard.compact-ports-hover-independence.test.tsx @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import { act, type ReactNode } from 'react' -import React from 'react' +import React, { act, type ReactNode } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { DashboardAgentRow as DashboardAgentRowData } from '@/components/dashboard/useDashboardData' diff --git a/src/renderer/src/components/sidebar/WorktreeOpenInMenu.tsx b/src/renderer/src/components/sidebar/WorktreeOpenInMenu.tsx index 0d6cf3245..d52422112 100644 --- a/src/renderer/src/components/sidebar/WorktreeOpenInMenu.tsx +++ b/src/renderer/src/components/sidebar/WorktreeOpenInMenu.tsx @@ -13,6 +13,7 @@ import { isLocalPathOpenBlocked, showLocalPathOpenBlockedToast } from '@/lib/loc import { getLocalFileManagerLabel } from '@/lib/local-file-manager-label' import { OpenInApplicationIcon } from '@/lib/open-in-app-catalog' import { getExternalEditorOpenCapability } from '@/lib/external-editor-open-capability' +import { NO_OPEN_IN_APPLICATIONS } from '@/lib/open-in-application-selection' import type { ShellOpenExternalEditorResult } from '../../../../shared/shell-open-types' import type { GlobalSettings, OpenInApplication } from '../../../../shared/types' import { translate } from '@/i18n/i18n' @@ -34,7 +35,7 @@ export type OpenInMenuEntry = { } export function getWorktreeOpenInEntries( - openInApplications: OpenInApplication[], + openInApplications: readonly OpenInApplication[], fileManagerLabel: string ): OpenInMenuEntry[] { return [ @@ -302,7 +303,9 @@ export function WorktreeOpenInMenuItems({ labelPrefix = '' }: WorktreeOpenInMenuItemsProps): React.JSX.Element { const openInWorktreePath = useOpenInWorktreePath({ worktreePath, connectionId }) - const openInApplications = useAppStore((s) => s.settings?.openInApplications ?? []) + const openInApplications = useAppStore( + (s) => s.settings?.openInApplications ?? NO_OPEN_IN_APPLICATIONS + ) const settings = useAppStore((s) => s.settings) const fileManagerLabel = getLocalFileManagerLabel() const entries = getWorktreeOpenInEntries(openInApplications, fileManagerLabel) diff --git a/src/renderer/src/components/status-bar/tooltip.tsx b/src/renderer/src/components/status-bar/tooltip.tsx index fecb07d0d..db585ab64 100644 --- a/src/renderer/src/components/status-bar/tooltip.tsx +++ b/src/renderer/src/components/status-bar/tooltip.tsx @@ -197,6 +197,46 @@ export function barColor(usedPct: number): string { return 'bg-red-500' } +function ProviderRateLimitWindowSection({ + window, + label, + textClass, + mutedClass, + emptyBarClass, + usagePercentageDisplay +}: { + window: RateLimitWindow | null + label: string + textClass: string + mutedClass: string + emptyBarClass: string + usagePercentageDisplay: UsagePercentageDisplay +}): React.JSX.Element | null { + if (!window) { + return null + } + const usedPct = clampUsedPercent(window.usedPercent) + const displayedPct = getDisplayedUsagePercentage(usedPct, usagePercentageDisplay) + const resetLabel = window.resetsAt ? formatResetCountdown(window.resetsAt - Date.now()) : null + + return ( +
+
{label}
+
+ {/* Why: fill follows the selected percentage; color still signals consumption urgency. */} +
+
+
+ {formatUsagePercentageLabel(usedPct, usagePercentageDisplay)} + {resetLabel && {resetLabel}} +
+
+ ) +} + export function ProviderPanel({ p, inverted = false, @@ -268,38 +308,6 @@ export function ProviderPanel({ ? formatResetCreditExpiry(p.rateLimitResetCredits?.nextExpiresAt, resetCreditCount) : null - const PanelWindowSection = ({ - w, - label - }: { - w: RateLimitWindow | null - label: string - }): React.JSX.Element | null => { - if (!w) { - return null - } - const usedPct = clampUsedPercent(w.usedPercent) - const displayedPct = getDisplayedUsagePercentage(usedPct, usagePercentageDisplay) - const resetLabel = w.resetsAt ? formatResetCountdown(w.resetsAt - Date.now()) : null - - return ( -
-
{label}
-
- {/* Why: fill follows the selected percentage; color still signals consumption urgency. */} -
-
-
- {formatUsagePercentageLabel(usedPct, usagePercentageDisplay)} - {resetLabel && {resetLabel}} -
-
- ) - } - return (
@@ -328,7 +336,15 @@ export function ProviderPanel({
{getWindowSections(p).map((s) => ( - + ))} {p.error ? ( diff --git a/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx b/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx index 87d12c712..d2e086f74 100644 --- a/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx +++ b/src/renderer/src/components/tab-bar/QuickLaunchButton.tsx @@ -10,7 +10,10 @@ import { useOptionalShortcutLabel } from '@/hooks/useShortcutLabel' import { launchAgentInNewTab } from '@/lib/launch-agent-in-new-tab' import type { TuiAgent } from '../../../../shared/types' import type { LaunchSource } from '../../../../shared/telemetry-events' -import { filterEnabledTuiAgents } from '../../../../shared/tui-agent-selection' +import { + DEFAULT_DISABLED_TUI_AGENTS, + filterEnabledTuiAgents +} from '../../../../shared/tui-agent-selection' import { translate } from '@/i18n/i18n' export type QuickLaunchAgentMenuItemsProps = { @@ -107,7 +110,9 @@ function QuickLaunchAgentMenuItemsInner({ const agentDetectionTarget = useAgentDetectionTargetForWorktree(worktreeId) const { detectedIds } = useDetectedAgents(agentDetectionTarget) const defaultAgent = useAppStore((s) => s.settings?.defaultTuiAgent) - const disabledAgents = useAppStore((s) => s.settings?.disabledTuiAgents ?? []) + const disabledAgents = useAppStore( + (s) => s.settings?.disabledTuiAgents ?? DEFAULT_DISABLED_TUI_AGENTS + ) const openSettingsPage = useAppStore((s) => s.openSettingsPage) const openSettingsTarget = useAppStore((s) => s.openSettingsTarget) const newAgentShortcut = useOptionalShortcutLabel('tab.newAgent') diff --git a/src/renderer/src/components/tab-bar/TabBar.tsx b/src/renderer/src/components/tab-bar/TabBar.tsx index e9eb7a96f..2ebecc589 100644 --- a/src/renderer/src/components/tab-bar/TabBar.tsx +++ b/src/renderer/src/components/tab-bar/TabBar.tsx @@ -207,7 +207,7 @@ function getTabLayoutSignature( return `${item.type}:${item.id}:${item.isPinned}:${isExpanded}:${Boolean(item.data.color)}:${label}` } if (item.type === 'browser') { - return `${item.type}:${item.id}:${item.isPinned}:${item.data.loading}:${item.data.loadError}:${label}` + return `${item.type}:${item.id}:${item.isPinned}:${item.data.loading}:${Boolean(item.data.loadError)}:${label}` } if (item.type === 'editor') { return `${item.type}:${item.id}:${item.isPinned}:${item.data.isDirty}:${item.data.isPreview}:${item.data.externalMutation ?? ''}:${status ?? ''}:${label}` diff --git a/src/renderer/src/components/tab-group/tab-drag-data.ts b/src/renderer/src/components/tab-group/tab-drag-data.ts new file mode 100644 index 000000000..ad55f0edd --- /dev/null +++ b/src/renderer/src/components/tab-group/tab-drag-data.ts @@ -0,0 +1,56 @@ +import type { TabGroup, TuiAgent } from '../../../../shared/types' +import type { TabSplitDirection } from '../../store/slices/tabs' + +export type TabDropZone = 'center' | TabSplitDirection + +export type TabDragItemData = { + kind: 'tab' + worktreeId: string + groupId: string + unifiedTabId: string + visibleTabId: string + tabType: 'terminal' | 'editor' | 'browser' | 'simulator' + label: string + iconPath?: string + color?: string | null + agent?: TuiAgent | null +} + +export type TabPaneDropData = { + kind: 'pane-body' + worktreeId: string + groupId: string +} + +export function canDropTabIntoPaneBody({ + activeDrag, + groupsByWorktree, + overGroupId, + worktreeId +}: { + activeDrag: TabDragItemData | null + groupsByWorktree: Record + overGroupId: string + worktreeId: string +}): boolean { + if (!activeDrag || activeDrag.worktreeId !== worktreeId) { + return false + } + + const overGroup = (groupsByWorktree[worktreeId] ?? []).find((group) => group.id === overGroupId) + if (!overGroup) { + return false + } + + return activeDrag.groupId !== overGroupId || overGroup.tabOrder.length > 1 +} + +export function isTabDragData(value: unknown): value is TabDragItemData { + return Boolean(value) && typeof value === 'object' && (value as TabDragItemData).kind === 'tab' +} + +export function isPaneDropData(value: unknown): value is TabPaneDropData { + return ( + Boolean(value) && typeof value === 'object' && (value as TabPaneDropData).kind === 'pane-body' + ) +} diff --git a/src/renderer/src/components/tab-group/tab-drag-preview-target.ts b/src/renderer/src/components/tab-group/tab-drag-preview-target.ts index b4c36a0dc..7b035ea2f 100644 --- a/src/renderer/src/components/tab-group/tab-drag-preview-target.ts +++ b/src/renderer/src/components/tab-group/tab-drag-preview-target.ts @@ -1,5 +1,4 @@ -import type { TabDragItemData } from './useTabDragSplit' -import { isPaneDropData, isTabDragData } from './useTabDragSplit' +import { isPaneDropData, isTabDragData, type TabDragItemData } from './tab-drag-data' export type DragPreviewTabTarget = { groupId: string diff --git a/src/renderer/src/components/tab-group/tab-drop-zone.ts b/src/renderer/src/components/tab-group/tab-drop-zone.ts index 10291e238..a30c592bc 100644 --- a/src/renderer/src/components/tab-group/tab-drop-zone.ts +++ b/src/renderer/src/components/tab-group/tab-drop-zone.ts @@ -1,4 +1,4 @@ -import type { TabDropZone } from './useTabDragSplit' +import type { TabDropZone } from './tab-drag-data' /** Matches TabGroupPanel tab row height (`h-[32px]`). */ export const TAB_GROUP_TAB_STRIP_HEIGHT_PX = 32 diff --git a/src/renderer/src/components/tab-group/tab-group-panel-split-target.ts b/src/renderer/src/components/tab-group/tab-group-panel-split-target.ts index b78f383f2..80fefad40 100644 --- a/src/renderer/src/components/tab-group/tab-group-panel-split-target.ts +++ b/src/renderer/src/components/tab-group/tab-group-panel-split-target.ts @@ -11,7 +11,7 @@ import { isPaneDropData, isTabDragData, type TabDragItemData -} from './useTabDragSplit' +} from './tab-drag-data' export type TabGroupPanelGeometryEntry = { groupId: string diff --git a/src/renderer/src/components/tab-group/tab-insertion.ts b/src/renderer/src/components/tab-group/tab-insertion.ts index 708e4616b..5ae542403 100644 --- a/src/renderer/src/components/tab-group/tab-insertion.ts +++ b/src/renderer/src/components/tab-group/tab-insertion.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react' import type { DragEndEvent, DragMoveEvent, DragOverEvent } from '@dnd-kit/core' -import type { TabDragItemData } from './useTabDragSplit' +import type { TabDragItemData } from './tab-drag-data' // Why: when a tab is dragged over another tab's sortable rect, we compute // which side of the hovered tab the drop will land on (before vs. after). diff --git a/src/renderer/src/components/tab-group/useTabDragSplit.ts b/src/renderer/src/components/tab-group/useTabDragSplit.ts index 090e41b04..ce79cf032 100644 --- a/src/renderer/src/components/tab-group/useTabDragSplit.ts +++ b/src/renderer/src/components/tab-group/useTabDragSplit.ts @@ -14,9 +14,8 @@ import { useSensor, useSensors } from '@dnd-kit/core' -import type { TabGroup, TuiAgent } from '../../../../shared/types' +import type { TabGroup } from '../../../../shared/types' import { useAppStore } from '../../store' -import type { TabSplitDirection } from '../../store/slices/tabs' import { mirrorWebRuntimeTabMove } from '../tab-bar/web-runtime-tab-move-mirror' import { resolveTabInsertion, @@ -40,79 +39,34 @@ import { type ActivePaneColumnSplitTarget, type TabGroupPanelGeometrySnapshot } from './tab-group-panel-split-target' +import { + canDropTabIntoPaneBody, + isPaneDropData, + isTabDragData, + type TabDragItemData, + type TabDropZone +} from './tab-drag-data' export type { HoveredTabInsertion } - -export type TabDropZone = 'center' | TabSplitDirection +export { + canDropTabIntoPaneBody, + isPaneDropData, + isTabDragData, + type TabDragItemData, + type TabDropZone, + type TabPaneDropData +} from './tab-drag-data' // Why: tab activation waits for pointerup, so dnd-kit needs enough movement // tolerance to avoid treating ordinary click jitter as an intentional drag. export const TAB_DRAG_ACTIVATION_DISTANCE_PX = 12 -export type TabDragItemData = { - kind: 'tab' - worktreeId: string - groupId: string - unifiedTabId: string - visibleTabId: string - tabType: 'terminal' | 'editor' | 'browser' | 'simulator' - /** Rendered by the DragOverlay ghost that follows the cursor across - * groups. Source tab strips use overflow-hidden, so without the overlay - * the dragged tab would be invisible once the cursor leaves its own - * group's strip. */ - label: string - iconPath?: string - color?: string | null - /** Coding-harness agent running in a terminal tab, so the drag ghost shows - * the provider glyph and matches the resting tab. Resolved per-tab in - * SortableTab (not at the TabBar level) to avoid re-rendering the whole tab - * strip on every agent-status ping. */ - agent?: TuiAgent | null -} - -export type TabPaneDropData = { - kind: 'pane-body' - worktreeId: string - groupId: string -} - export type HoveredTabDropTarget = { groupId: string zone: TabDropZone panelRect?: DOMRect } -export function canDropTabIntoPaneBody({ - activeDrag, - groupsByWorktree, - overGroupId, - worktreeId -}: { - activeDrag: TabDragItemData | null - groupsByWorktree: Record - overGroupId: string - worktreeId: string -}): boolean { - if (!activeDrag || activeDrag.worktreeId !== worktreeId) { - return false - } - - const overGroup = (groupsByWorktree[worktreeId] ?? []).find((group) => group.id === overGroupId) - if (!overGroup) { - return false - } - - // Why: splitting the only tab in a group onto that same group's body is a - // visual no-op. The store already rejects that drop, so the hover layer must - // suppress the pane overlay too or the user sees a split affordance that can - // never produce a layout change. - if (activeDrag.groupId === overGroupId && overGroup.tabOrder.length <= 1) { - return false - } - - return true -} - export function canDropTabForPaneColumnSplit(args: { activeDrag: TabDragItemData | null groupsByWorktree: Record @@ -130,16 +84,6 @@ export function canDropTabForPaneColumnSplit(args: { }) } -export function isTabDragData(value: unknown): value is TabDragItemData { - return Boolean(value) && typeof value === 'object' && (value as TabDragItemData).kind === 'tab' -} - -export function isPaneDropData(value: unknown): value is TabPaneDropData { - return ( - Boolean(value) && typeof value === 'object' && (value as TabPaneDropData).kind === 'pane-body' - ) -} - const collisionDetection: CollisionDetection = (args) => { const pointerCollisions = pointerWithin(args) return pointerCollisions.length > 0 ? pointerCollisions : closestCenter(args) diff --git a/src/renderer/src/components/terminal-pane/keyboard-handlers.ts b/src/renderer/src/components/terminal-pane/keyboard-handlers.ts index 2578d6575..1ea250715 100644 --- a/src/renderer/src/components/terminal-pane/keyboard-handlers.ts +++ b/src/renderer/src/components/terminal-pane/keyboard-handlers.ts @@ -666,6 +666,7 @@ export function useTerminalKeyboardShortcuts({ keyboardScopeRef, managerRef, paneTransportsRef, + panePtyBindingsRef, paneCwdRef, fallbackCwd, expandedPaneIdRef, diff --git a/src/renderer/src/components/terminal-pane/pty-connection.test.ts b/src/renderer/src/components/terminal-pane/pty-connection.test.ts index caf324b08..4d6431880 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.test.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.test.ts @@ -942,7 +942,7 @@ describe('connectPanePty', () => { afterEach(async () => { // Why: drain in-flight foreground-confirm microtasks while this test still owns the store mock, so its async fallout can't leak into (and flake) the next test. - await flushAsyncTicks() + await flushAsyncTicks(20) vi.useRealTimers() vi.restoreAllMocks() if (originalRequestAnimationFrame) { diff --git a/src/renderer/src/components/terminal-pane/useSessionRestoredBannerDismiss.test.tsx b/src/renderer/src/components/terminal-pane/useSessionRestoredBannerDismiss.test.tsx index 3c13aea87..b10ba51ca 100644 --- a/src/renderer/src/components/terminal-pane/useSessionRestoredBannerDismiss.test.tsx +++ b/src/renderer/src/components/terminal-pane/useSessionRestoredBannerDismiss.test.tsx @@ -1,7 +1,6 @@ // @vitest-environment happy-dom -import { useRef } from 'react' -import { act } from 'react' +import { useRef, act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, describe, expect, it, vi } from 'vitest' import { useSessionRestoredBannerDismiss } from './useSessionRestoredBannerDismiss' diff --git a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx index ce77f8d1e..59bce894d 100644 --- a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx +++ b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandDialog.tsx @@ -2,7 +2,8 @@ import { useRef, useState } from 'react' import type { Repo, TerminalQuickCommand, - TerminalQuickCommandScope + TerminalQuickCommandScope, + TuiAgent } from '../../../../shared/types' import { getTerminalQuickCommandAction, @@ -21,7 +22,6 @@ import { import { Label } from '@/components/ui/label' import { getAgentCatalog } from '@/lib/agent-catalog' import { getScreenSubmitShortcutLabel, isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' -import type { TuiAgent } from '../../../../shared/types' import { TerminalQuickCommandActionToggle } from './TerminalQuickCommandActionToggle' import { TerminalQuickCommandAdvancedSection } from './TerminalQuickCommandAdvancedSection' import { TerminalQuickCommandContentSection } from './TerminalQuickCommandContentSection' diff --git a/src/renderer/src/hooks/useIpcEvents.ts b/src/renderer/src/hooks/useIpcEvents.ts index 66b4ffd24..dea9f82c2 100644 --- a/src/renderer/src/hooks/useIpcEvents.ts +++ b/src/renderer/src/hooks/useIpcEvents.ts @@ -16,7 +16,10 @@ import { OPEN_WORKSPACE_BOARD_EVENT } from '@/components/sidebar/useWorkspaceBoa import { SPLIT_TERMINAL_PANE_EVENT, CLOSE_TERMINAL_PANE_EVENT } from '@/constants/terminal' import { requestBackgroundTerminalWorktreeMount } from '@/components/terminal/background-terminal-worktree-mount' import { planMobileTerminalTabMount } from '@/lib/mobile-terminal-tab-mount' -import { hasRegisteredRuntimeTerminalTab } from '@/runtime/sync-runtime-graph' +import { + hasRegisteredRuntimeTerminalTab, + focusRuntimeTerminalSurface +} from '@/runtime/sync-runtime-graph' import type { SplitTerminalPaneDetail, CloseTerminalPaneDetail } from '@/constants/terminal' import { getVisibleWorktreeIds } from '@/components/sidebar/visible-worktrees' import { activateTabNumberShortcut } from '@/lib/tab-number-shortcuts' @@ -72,7 +75,6 @@ import { TOGGLE_FLOATING_TERMINAL_EVENT } from '@/lib/floating-terminal' import { TOGGLE_QUICK_COMMANDS_MENU_EVENT } from '@/lib/quick-commands-menu-events' import { focusTerminalTabSurface } from '@/lib/focus-terminal-tab-surface' import { activateTabAndFocusPane } from '@/lib/activate-tab-and-focus-pane' -import { focusRuntimeTerminalSurface } from '@/runtime/sync-runtime-graph' import { getRuntimeEnvironmentConnectionGeneration } from '@/store/slices/runtime-status' import { getEnvironmentSshStateGeneration } from '@/store/slices/runtime-environment-ssh' import { getRuntimeEnvironmentRevision } from '@/runtime/runtime-environment-revision' diff --git a/src/renderer/src/lib/agent-paste-draft.ts b/src/renderer/src/lib/agent-paste-draft.ts index e92463108..86b0472c9 100644 --- a/src/renderer/src/lib/agent-paste-draft.ts +++ b/src/renderer/src/lib/agent-paste-draft.ts @@ -1,4 +1,4 @@ -import type { TuiAgent } from '../../../shared/types' +import type { TuiAgent, GlobalSettings } from '../../../shared/types' import { TUI_AGENT_CONFIG } from '../../../shared/tui-agent-config' import { useAppStore } from '@/store' import { @@ -12,7 +12,6 @@ import { } from '@/components/terminal-pane/terminal-bracketed-paste' import { waitForAgentReady } from './agent-ready-wait' import { getSettingsForWorktreeRuntimeOwner } from './worktree-runtime-owner' -import type { GlobalSettings } from '../../../shared/types' import { sendAgentDraftPasteContent } from './agent-draft-paste-content' import { agentDeliversDraftViaNativePrefill } from './agent-native-draft-prefill' import { waitForAgentDraftInputReady } from './agent-draft-readiness' diff --git a/src/renderer/src/lib/ephemeral-vm-workspace-target.ts b/src/renderer/src/lib/ephemeral-vm-workspace-target.ts index f8cfdebe1..1e4e0aec2 100644 --- a/src/renderer/src/lib/ephemeral-vm-workspace-target.ts +++ b/src/renderer/src/lib/ephemeral-vm-workspace-target.ts @@ -3,10 +3,8 @@ import type { ProjectHostSetupExistingFolderArgs, ProjectHostSetupResult } from '../../../shared/types' -import { - getEphemeralVmRecipeResultProjectRoot, - type EphemeralVmRecipeResultWarning -} from '../../../shared/ephemeral-vm-recipes' +import { getEphemeralVmRecipeResultProjectRoot } from '../../../shared/ephemeral-vm-recipes' +import type { EphemeralVmRecipeResultWarning } from '../../../shared/ephemeral-vm-recipe-diagnostics' import { PROJECT_HOST_SETUP_RUNTIME_CAPABILITY } from '../../../shared/protocol-version' import { translate } from '@/i18n/i18n' import { assertRuntimeEnvironmentCapability } from '@/runtime/runtime-rpc-client' diff --git a/src/renderer/src/lib/github-work-item-background-create.ts b/src/renderer/src/lib/github-work-item-background-create.ts index 260cd2ad6..d050cbf1b 100644 --- a/src/renderer/src/lib/github-work-item-background-create.ts +++ b/src/renderer/src/lib/github-work-item-background-create.ts @@ -29,8 +29,7 @@ import { MIN_COMPATIBLE_RUNTIME_SERVER_VERSION, RUNTIME_PROTOCOL_VERSION } from '../../../shared/protocol-version' -import type { GitHubWorkItem, SetupDecision } from '../../../shared/types' -import type { Repo } from '../../../shared/types' +import type { GitHubWorkItem, SetupDecision, Repo } from '../../../shared/types' import type { TaskSourceContext, WorkspaceRunContext } from '../../../shared/task-source-context' import { resolveGitHubWorkItemIdentity } from '@/lib/github-work-item-identity' diff --git a/src/renderer/src/lib/open-in-application-selection.ts b/src/renderer/src/lib/open-in-application-selection.ts new file mode 100644 index 000000000..09ccaca9b --- /dev/null +++ b/src/renderer/src/lib/open-in-application-selection.ts @@ -0,0 +1,3 @@ +import type { OpenInApplication } from '../../../shared/types' + +export const NO_OPEN_IN_APPLICATIONS: readonly OpenInApplication[] = [] diff --git a/src/renderer/src/lib/pane-manager/pane-manager-types.ts b/src/renderer/src/lib/pane-manager/pane-manager-types.ts index 2c1fd67de..4f409f166 100644 --- a/src/renderer/src/lib/pane-manager/pane-manager-types.ts +++ b/src/renderer/src/lib/pane-manager/pane-manager-types.ts @@ -1,5 +1,4 @@ -import type { IDisposable, IMarker, Terminal } from '@xterm/xterm' -import type { ITerminalOptions } from '@xterm/xterm' +import type { IDisposable, IMarker, Terminal, ITerminalOptions } from '@xterm/xterm' import type { FitAddon } from '@xterm/addon-fit' import type { LigaturesAddon } from '@xterm/addon-ligatures' import type { SearchAddon } from '@xterm/addon-search' diff --git a/src/renderer/src/lib/repo-slug-index.ts b/src/renderer/src/lib/repo-slug-index.ts index 34c88bec3..ed1dbe5f5 100644 --- a/src/renderer/src/lib/repo-slug-index.ts +++ b/src/renderer/src/lib/repo-slug-index.ts @@ -15,8 +15,7 @@ // process (`repoSlug` reads `.git/config`). import { useEffect, useMemo, useRef, useState } from 'react' import { useAppStore } from '@/store' -import type { Repo } from '../../../shared/types' -import type { GlobalSettings } from '../../../shared/types' +import type { Repo, GlobalSettings } from '../../../shared/types' import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' import { clearRepoSlugCacheValues, diff --git a/src/renderer/src/lib/worktree-operation-generation.ts b/src/renderer/src/lib/worktree-operation-generation.ts index 04a2bd3bf..67a093023 100644 --- a/src/renderer/src/lib/worktree-operation-generation.ts +++ b/src/renderer/src/lib/worktree-operation-generation.ts @@ -1,6 +1,8 @@ import { parseExecutionHostId } from '../../../shared/execution-host' -import { getEnvironmentSshStateGeneration } from '@/store/slices/runtime-environment-ssh' -import { getEnvironmentSshTargetConnectionGeneration } from '@/store/slices/runtime-environment-ssh' +import { + getEnvironmentSshStateGeneration, + getEnvironmentSshTargetConnectionGeneration +} from '@/store/slices/runtime-environment-ssh' import { getLocalSshTargetConnectionGeneration } from '@/store/slices/ssh' import { getRuntimeEnvironmentConnectionGeneration } from '@/store/slices/runtime-status' import { getRuntimeEnvironmentRevision } from '@/runtime/runtime-environment-revision' diff --git a/src/renderer/src/runtime/web-runtime-session.ts b/src/renderer/src/runtime/web-runtime-session.ts index cb159aaaa..9bb2b3bed 100644 --- a/src/renderer/src/runtime/web-runtime-session.ts +++ b/src/renderer/src/runtime/web-runtime-session.ts @@ -14,8 +14,10 @@ import type { } from '../../../shared/runtime-types' import type { TerminalPaneSplitSource } from '../../../shared/feature-education-telemetry' import type { StartupCommandDelivery } from '../../../shared/codex-startup-delivery' -import type { SleepingAgentLaunchConfig } from '../../../shared/agent-session-resume' -import type { AgentProviderSessionMetadata } from '../../../shared/agent-session-resume' +import type { + SleepingAgentLaunchConfig, + AgentProviderSessionMetadata +} from '../../../shared/agent-session-resume' import { AGENT_SESSION_OMP_RESUME_PATH_RUNTIME_CAPABILITY } from '../../../shared/protocol-version' import type { AgentLaunchPreferences, diff --git a/src/renderer/src/store/slices/github-pr-refresh-host-guard.test.ts b/src/renderer/src/store/slices/github-pr-refresh-host-guard.test.ts index b18cd4839..d785560f3 100644 --- a/src/renderer/src/store/slices/github-pr-refresh-host-guard.test.ts +++ b/src/renderer/src/store/slices/github-pr-refresh-host-guard.test.ts @@ -35,12 +35,15 @@ function createTestStore() { ) } -function seed(store: ReturnType, repo: Record) { +function seed( + store: ReturnType, + repo: Record & { id: string; path: string } +) { store.setState({ settings: { activeRuntimeEnvironmentId: null } as never, repos: [repo], worktreesByRepo: { - [repo.id as string]: [ + [repo.id]: [ { id: 'wt-1', repoId: repo.id, diff --git a/src/renderer/src/store/slices/runtime-status.test.ts b/src/renderer/src/store/slices/runtime-status.test.ts index 2ec228599..e3a71e58f 100644 --- a/src/renderer/src/store/slices/runtime-status.test.ts +++ b/src/renderer/src/store/slices/runtime-status.test.ts @@ -6,8 +6,11 @@ import { callRuntimeRpc, clearRuntimeCompatibilityCacheForTests } from '../../runtime/runtime-rpc-client' -import { createRuntimeStatusSlice, type RuntimeStatusSlice } from './runtime-status' -import { getRuntimeEnvironmentConnectionGeneration } from './runtime-status' +import { + createRuntimeStatusSlice, + type RuntimeStatusSlice, + getRuntimeEnvironmentConnectionGeneration +} from './runtime-status' function createSliceStore() { return create()((...a) => ({ diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts index b01292b9b..2d8976d33 100644 --- a/src/renderer/src/web/web-preload-api.ts +++ b/src/renderer/src/web/web-preload-api.ts @@ -1064,7 +1064,7 @@ function writeWebKeybindingAction( bindings: string[] | null ): KeybindingFileSnapshot { if (!isKeybindingActionId(actionId)) { - throw new Error(`Unknown keybinding action "${actionId}".`) + throw new Error(`Unknown keybinding action "${String(actionId)}".`) } const normalizedBindings = bindings === null ? null : normalizeKeybindingArrayForAction(actionId, bindings) diff --git a/src/shared/agent-hook-listener.ts b/src/shared/agent-hook-listener.ts index 7701ea6b1..d26268d12 100644 --- a/src/shared/agent-hook-listener.ts +++ b/src/shared/agent-hook-listener.ts @@ -2266,6 +2266,7 @@ function isNewTurnEvent(source: AgentHookSource, eventName: unknown): boolean { switch (source) { case 'claude': // Why: Kimi Code emits Claude-compatible hook events, so UserPromptSubmit is its new-turn boundary too. + // falls through case 'kimi': return eventName === 'UserPromptSubmit' case 'codex': @@ -2359,6 +2360,7 @@ function extractToolFields( switch (source) { case 'claude': // Why: Kimi Code uses Claude's tool_name/tool_input payload fields verbatim. + // falls through case 'kimi': return extractClaudeToolFields(eventName, hookPayload) case 'codex': diff --git a/src/shared/agent-session-resume.ts b/src/shared/agent-session-resume.ts index 87bb9d389..50581c5c9 100644 --- a/src/shared/agent-session-resume.ts +++ b/src/shared/agent-session-resume.ts @@ -186,6 +186,7 @@ export function extractAgentProviderSession( case 'gemini': case 'droid': // Why: Kimi Code posts a Claude-shaped `session_id` (e.g. session_). + // falls through case 'kimi': { const id = readSessionId(payload, ['session_id']) return id ? { key: 'session_id', id } : null diff --git a/src/shared/ai-vault-types.ts b/src/shared/ai-vault-types.ts index f91951472..530c84421 100644 --- a/src/shared/ai-vault-types.ts +++ b/src/shared/ai-vault-types.ts @@ -323,6 +323,7 @@ function buildAgentResumeInvocation( // Why: Kimi Code resumes with `kimi --session ` (alias `-S`). Sessions // are work-dir-scoped, so the cwd prefix from buildAiVaultResumeCommand is // required — resuming from another directory is rejected by the CLI. + // falls through case 'kimi': return `${baseCommand} --session ${sessionArg}` case 'copilot': @@ -337,6 +338,7 @@ function buildAgentResumeInvocation( case 'droid': // Why: OMP resumes by absolute transcript path (see buildAiVaultResumeCommand), // but the `--resume ` invocation form is identical to the others here. + // falls through case 'omp': return `${baseCommand} --resume ${sessionArg}` case 'antigravity': diff --git a/src/shared/ephemeral-vm-recipes.test.ts b/src/shared/ephemeral-vm-recipes.test.ts index b074c4383..d83ad44bb 100644 --- a/src/shared/ephemeral-vm-recipes.test.ts +++ b/src/shared/ephemeral-vm-recipes.test.ts @@ -2,11 +2,13 @@ import { describe, expect, it, vi } from 'vitest' import { encodePairingOffer, PAIRING_OFFER_VERSION } from './pairing' import { EPHEMERAL_VM_RECIPE_JSON_STRUCTURE_LIMITS, + parseEphemeralVmRecipeResult +} from './ephemeral-vm-recipes' +import { getEphemeralVmRecipeResultWarnings, - parseEphemeralVmRecipeResult, redactEphemeralVmRecipeDiagnosticText, redactEphemeralVmRecipeResultForDiagnostics -} from './ephemeral-vm-recipes' +} from './ephemeral-vm-recipe-diagnostics' function makePairingCode(endpoint = 'wss://sandbox.example.com'): string { return encodePairingOffer({ diff --git a/src/shared/ephemeral-vm-recipes.ts b/src/shared/ephemeral-vm-recipes.ts index 0f68bc92f..071f1e89c 100644 --- a/src/shared/ephemeral-vm-recipes.ts +++ b/src/shared/ephemeral-vm-recipes.ts @@ -6,16 +6,6 @@ import { MIN_SSH_RELAY_GRACE_PERIOD_SECONDS } from './ssh-types' import { assertJsonTextStructureWithinLimits } from './json-text-structure-limit' -// Why: ephemeral-vm-recipe-doctor imports Node's fs/path, so it must NOT be -// re-exported through this barrel — the renderer/web-client imports this module -// and would otherwise pull Node built-ins into the browser bundle (build fails). -// Node callers import doctorEphemeralVmRecipe directly from the doctor module. -export { - getEphemeralVmRecipeResultWarnings, - redactEphemeralVmRecipeDiagnosticText, - redactEphemeralVmRecipeResultForDiagnostics -} from './ephemeral-vm-recipe-diagnostics' -export type { EphemeralVmRecipeResultWarning } from './ephemeral-vm-recipe-diagnostics' const JsonValueSchema: z.ZodType = z.lazy(() => z.union([ diff --git a/src/shared/external-worktree-inbox.ts b/src/shared/external-worktree-inbox.ts index 6b6fdaa74..56aea01ff 100644 --- a/src/shared/external-worktree-inbox.ts +++ b/src/shared/external-worktree-inbox.ts @@ -3,7 +3,7 @@ import type { DetectedWorktree, DetectedWorktreeListResult, Repo } from './types import { effectiveExternalWorktreeVisibility, isLegacyRepoForExternalWorktreeVisibility -} from './worktree-ownership' +} from './external-worktree-visibility' export function normalizeExternalWorktreeInboxPath(path: string): string { return normalizeRuntimePathForComparison(path) diff --git a/src/shared/external-worktree-visibility.ts b/src/shared/external-worktree-visibility.ts index d1a5d4be5..be5d24a52 100644 --- a/src/shared/external-worktree-visibility.ts +++ b/src/shared/external-worktree-visibility.ts @@ -1,5 +1,7 @@ import { normalizeRuntimePathSeparators } from './cross-platform-path' +import type { ExternalWorktreeVisibility, Repo } from './types' +export const EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT = Date.UTC(2026, 4, 23) export const UNKNOWN_EXTERNAL_WORKTREE_PARENT_PATH = 'Unknown location' function trimRuntimePathTrailingSlash(value: string): string { @@ -42,3 +44,26 @@ export function getExternalWorktreeParentPath(worktreePath: string | undefined): } return normalized.slice(0, lastSeparatorIndex) } + +export function isLegacyRepoForExternalWorktreeVisibility(repo: Repo): boolean { + if (typeof repo.externalWorktreeVisibilityLegacy === 'boolean') { + return repo.externalWorktreeVisibilityLegacy + } + if (repo.externalWorktreeVisibility === undefined) { + return true + } + if (!Number.isFinite(repo.addedAt)) { + return true + } + return repo.addedAt < EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT +} + +export function effectiveExternalWorktreeVisibility( + repo: Pick, + isLegacyRepoForVisibility: boolean +): ExternalWorktreeVisibility { + if (repo.externalWorktreeVisibility) { + return repo.externalWorktreeVisibility + } + return isLegacyRepoForVisibility ? 'show' : 'hide' +} diff --git a/src/shared/remote-runtime-shared-control-protocol.ts b/src/shared/remote-runtime-shared-control-protocol.ts index e4caf38da..4daae2159 100644 --- a/src/shared/remote-runtime-shared-control-protocol.ts +++ b/src/shared/remote-runtime-shared-control-protocol.ts @@ -1,5 +1,4 @@ -import { decrypt } from './e2ee-crypto' -import { encrypt } from './e2ee-crypto' +import { decrypt, encrypt } from './e2ee-crypto' import type WebSocket from 'ws' import { RemoteRuntimeClientError } from './remote-runtime-client' import { serializeRemoteRuntimePayload } from './remote-runtime-memory-limits' diff --git a/src/shared/runtime-types.ts b/src/shared/runtime-types.ts index 466793915..2ef276328 100644 --- a/src/shared/runtime-types.ts +++ b/src/shared/runtime-types.ts @@ -23,9 +23,9 @@ import type { Worktree, WorktreeLineage, WorkspaceLineage, - WorktreeLineageWarning + WorktreeLineageWarning, + TerminalPaneLayoutNode } from './types' -import type { TerminalPaneLayoutNode } from './types' import type { RuntimeMarkdownReadTabResult, RuntimeMarkdownSaveTabResult diff --git a/src/shared/worktree-ownership.ts b/src/shared/worktree-ownership.ts index 1b04a0528..a14523c4d 100644 --- a/src/shared/worktree-ownership.ts +++ b/src/shared/worktree-ownership.ts @@ -12,9 +12,12 @@ import { type AgentScratchWorktreePathMatcher } from './agent-scratch-worktrees' import { isExplicitlyImportedExternalWorktreePath } from './external-worktree-inbox' +import { + effectiveExternalWorktreeVisibility, + isLegacyRepoForExternalWorktreeVisibility +} from './external-worktree-visibility' import type { DetectedWorktree, - ExternalWorktreeVisibility, GlobalSettings, OrcaWorkspaceLayout, Repo, @@ -23,30 +26,11 @@ import type { WorktreeOwnership } from './types' -export const EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT = Date.UTC(2026, 4, 23) - -export function isLegacyRepoForExternalWorktreeVisibility(repo: Repo): boolean { - if (typeof repo.externalWorktreeVisibilityLegacy === 'boolean') { - return repo.externalWorktreeVisibilityLegacy - } - if (repo.externalWorktreeVisibility === undefined) { - return true - } - if (!Number.isFinite(repo.addedAt)) { - return true - } - return repo.addedAt < EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT -} - -export function effectiveExternalWorktreeVisibility( - repo: Pick, - isLegacyRepoForVisibility: boolean -): ExternalWorktreeVisibility { - if (repo.externalWorktreeVisibility) { - return repo.externalWorktreeVisibility - } - return isLegacyRepoForVisibility ? 'show' : 'hide' -} +export { + effectiveExternalWorktreeVisibility, + EXTERNAL_WORKTREE_VISIBILITY_ROLLOUT_AT, + isLegacyRepoForExternalWorktreeVisibility +} from './external-worktree-visibility' export function buildKnownOrcaWorkspaceLayouts( settings: Pick, diff --git a/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs b/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs index 137bc48de..3702ad21a 100644 --- a/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs +++ b/tests/e2e/fixtures/visible-tui-scroll-fixture.cjs @@ -69,7 +69,7 @@ process.stdin.on('data', (chunk) => { cleanup() } - pending += chunk + pending += chunk.toString() let match let lastIndex = 0 let reportsInChunk = 0 diff --git a/tests/e2e/terminal-codex-skill-preview-artifact-repro.spec.ts b/tests/e2e/terminal-codex-skill-preview-artifact-repro.spec.ts index a0ced0549..9d23fe600 100644 --- a/tests/e2e/terminal-codex-skill-preview-artifact-repro.spec.ts +++ b/tests/e2e/terminal-codex-skill-preview-artifact-repro.spec.ts @@ -1,5 +1,4 @@ -import { mkdirSync, writeFileSync } from 'node:fs' -import { realpathSync } from 'node:fs' +import { mkdirSync, writeFileSync, realpathSync } from 'node:fs' import path from 'node:path' import type { ElectronApplication, Page, TestInfo } from '@stablyai/playwright-test' import { test, expect } from './helpers/orca-app'