diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index 18c76c7476a..655574cd16f 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -2921,7 +2921,6 @@ "src/main/ipc/github-repo-routing-twin-host.test.ts": 91, "src/main/ipc/github-ssh-connection-routing.test.ts": 508, "src/main/ipc/github-star-telemetry.test.ts": 520, - "src/main/ipc/github-work-item-args.test.ts": 33, "src/main/ipc/github-wsl-runtime-routing.test.ts": 726, "src/main/ipc/gitlab-repo-access.test.ts": 111, "src/main/ipc/gitlab.test.ts": 181, @@ -10601,7 +10600,6 @@ "src/renderer/src/lib/ime-composition-keyboard-event.test.ts": 199, "src/renderer/src/lib/ime-overlay-escape.test.tsx": 615, "src/renderer/src/lib/ime-text-field.test.tsx": 463, - "src/renderer/src/lib/ios-web-platform.test.ts": 48, "src/renderer/src/lib/ipc-error.test.ts": 69, "src/renderer/src/lib/jira-board-drag-payload.test.ts": 59, "src/renderer/src/lib/jira-issue-workspace-attachment.test.ts": 95, diff --git a/src/main/browser/agent-browser-set-media-arguments.test.ts b/src/main/browser/agent-browser-set-media-arguments.test.ts deleted file mode 100644 index 8e00fafb53b..00000000000 --- a/src/main/browser/agent-browser-set-media-arguments.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { agentBrowserSetMediaArguments } from './agent-browser-set-media-arguments' - -describe('agentBrowserSetMediaArguments', () => { - it.each([ - [undefined, 'reduce', ['set', 'media', 'reduced-motion']], - [undefined, 'no-preference', ['set', 'media']], - ['dark', undefined, ['set', 'media', 'dark']], - ['light', 'reduce', ['set', 'media', 'light', 'reduced-motion']] - ])('maps color scheme %s and reduced motion %s', (colorScheme, reducedMotion, expected) => { - expect(agentBrowserSetMediaArguments(colorScheme, reducedMotion)).toEqual(expected) - }) - - it.each([ - ['sepia', undefined, 'Unsupported --color-scheme: sepia (expected dark or light)'], - [ - undefined, - 'reduced-motion', - 'Unsupported --reduced-motion: reduced-motion (expected reduce or no-preference)' - ] - ])('rejects color scheme %s and reduced motion %s', (colorScheme, reducedMotion, message) => { - expect(() => agentBrowserSetMediaArguments(colorScheme, reducedMotion)).toThrow( - expect.objectContaining({ code: 'invalid_argument', message }) - ) - }) -}) diff --git a/src/main/browser/agent-browser-wait-arguments.test.ts b/src/main/browser/agent-browser-wait-arguments.test.ts deleted file mode 100644 index f09f9df1d8c..00000000000 --- a/src/main/browser/agent-browser-wait-arguments.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { agentBrowserWaitArguments } from './agent-browser-wait-arguments' - -describe('agentBrowserWaitArguments', () => { - it.each([ - [ - { fn: 'window.ready', timeout: 60_000 }, - ['wait', '--fn', 'window.ready', '--timeout', '60000'], - 60_000, - 65_000 - ], - [{ selector: '#done' }, ['wait', '#done', '--timeout', '25000'], 25_000, 30_000], - [{ timeout: 40_000 }, ['wait', '40000'], undefined, undefined] - ])('builds %j', (options, args, conditionTimeoutMs, processTimeoutMs) => { - expect(agentBrowserWaitArguments(options)).toEqual({ - args, - conditionTimeoutMs, - processTimeoutMs - }) - }) -}) diff --git a/src/main/claude/claude-informational-row.test.ts b/src/main/claude/claude-informational-row.test.ts index 4e9070fd3aa..14d8040fa69 100644 --- a/src/main/claude/claude-informational-row.test.ts +++ b/src/main/claude/claude-informational-row.test.ts @@ -72,8 +72,4 @@ describe('a Claude informational frame', () => { expect(items).toEqual([{ kind: 'status', tone: 'warning', text: content }]) }) - - it('writes no row for a warning with nothing to say', async () => { - expect(await itemsFor([informational('warning', ' ')])).toEqual([]) - }) }) diff --git a/src/main/ipc/github-work-item-args.test.ts b/src/main/ipc/github-work-item-args.test.ts deleted file mode 100644 index 670e5013c85..00000000000 --- a/src/main/ipc/github-work-item-args.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { dispatchWorkItem } from './github-work-item-args' - -describe('dispatchWorkItem', () => { - const repo = { path: '/r', connectionId: null } - - it('rejects non-integer numbers', () => { - const fn = vi.fn() - expect(dispatchWorkItem({ repoPath: '/r', number: 1.5 }, repo, fn)).toBeNull() - expect(fn).not.toHaveBeenCalled() - }) - - it('rejects numbers < 1', () => { - const fn = vi.fn() - expect(dispatchWorkItem({ repoPath: '/r', number: 0 }, repo, fn)).toBeNull() - expect(dispatchWorkItem({ repoPath: '/r', number: -5 }, repo, fn)).toBeNull() - expect(fn).not.toHaveBeenCalled() - }) - - it('rejects non-number values coming across IPC', () => { - const fn = vi.fn() - // Model malformed renderer input despite the static type. - const bogus = { repoPath: '/r', number: 'abc' as unknown as number } - expect(dispatchWorkItem(bogus, repo, fn)).toBeNull() - expect(fn).not.toHaveBeenCalled() - }) - - it('coerces unknown type values to undefined', async () => { - const fn = vi.fn().mockResolvedValue(null) - const bogus = { - repoPath: '/r', - number: 42, - type: 'bogus' as unknown as 'issue' | 'pr' - } - await dispatchWorkItem(bogus, repo, fn) - expect(fn).toHaveBeenCalledWith('/r', 42, undefined, null, undefined, undefined) - }) - - it('passes valid issue type through', async () => { - const fn = vi.fn().mockResolvedValue(null) - await dispatchWorkItem({ repoPath: '/r', number: 42, type: 'issue' }, repo, fn) - expect(fn).toHaveBeenCalledWith('/r', 42, 'issue', null, undefined, undefined) - }) - - it('passes valid pr type through', async () => { - const fn = vi.fn().mockResolvedValue(null) - await dispatchWorkItem({ repoPath: '/r', number: 42, type: 'pr' }, repo, fn) - expect(fn).toHaveBeenCalledWith('/r', 42, 'pr', null, undefined, undefined) - }) - - it('passes SSH connection context through', async () => { - const fn = vi.fn().mockResolvedValue(null) - await dispatchWorkItem( - { repoPath: '/remote/repo', number: 42, type: 'issue' }, - { path: '/remote/repo', connectionId: 'ssh-1' }, - fn - ) - expect(fn).toHaveBeenCalledWith('/remote/repo', 42, 'issue', 'ssh-1', undefined, undefined) - }) - - it('pins the repo issue source preference for open-by-number', async () => { - const fn = vi.fn().mockResolvedValue(null) - await dispatchWorkItem( - { repoPath: '/r', number: 42, type: 'pr' }, - { path: '/r', connectionId: null, issueSourcePreference: 'origin' }, - fn, - { wslDistro: 'Ubuntu' } - ) - expect(fn).toHaveBeenCalledWith('/r', 42, 'pr', null, { wslDistro: 'Ubuntu' }, 'origin') - }) - - it('leaves upstream and auto preferences on the multi-candidate probe', async () => { - const fn = vi.fn().mockResolvedValue(null) - await dispatchWorkItem( - { repoPath: '/r', number: 7, type: 'pr' }, - { path: '/r', connectionId: null, issueSourcePreference: 'upstream' }, - fn - ) - await dispatchWorkItem( - { repoPath: '/r', number: 7, type: 'pr' }, - { path: '/r', connectionId: null, issueSourcePreference: 'auto' }, - fn - ) - expect(fn).toHaveBeenNthCalledWith(1, '/r', 7, 'pr', null, undefined, 'upstream') - expect(fn).toHaveBeenNthCalledWith(2, '/r', 7, 'pr', null, undefined, 'auto') - }) -}) diff --git a/src/main/runtime/orchestration/formatter.test.ts b/src/main/runtime/orchestration/formatter.test.ts index 483de15d18b..4273569ed91 100644 --- a/src/main/runtime/orchestration/formatter.test.ts +++ b/src/main/runtime/orchestration/formatter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { formatMessageBanner, formatMessagePointer, formatMessagesForInjection } from './formatter' +import { formatMessageBanner } from './formatter' import type { MessageRow } from './types' function makeMessage(overrides: Partial = {}): MessageRow { @@ -32,43 +32,6 @@ describe('formatMessageBanner', () => { expect(banner).not.toContain('[HIGH]') }) - it('includes [HIGH] tag for high priority', () => { - const banner = formatMessageBanner(makeMessage({ priority: 'high' })) - expect(banner).toContain('[HIGH]') - }) - - it('includes [URGENT] tag for urgent priority', () => { - const banner = formatMessageBanner(makeMessage({ priority: 'urgent' })) - expect(banner).toContain('[URGENT]') - }) - - it('includes body after subject', () => { - const banner = formatMessageBanner(makeMessage({ body: 'Some details here' })) - const lines = banner.split('\n') - const subjectIdx = lines.findIndex((l) => l.startsWith('Subject:')) - const bodyIdx = lines.indexOf('Some details here') - expect(subjectIdx).toBeGreaterThanOrEqual(0) - expect(bodyIdx).toBeGreaterThan(subjectIdx) - }) - - it('omits body line when body is empty', () => { - const banner = formatMessageBanner(makeMessage({ body: '' })) - const lines = banner.split('\n') - const subjectIdx = lines.findIndex((l) => l.startsWith('Subject:')) - expect(lines[subjectIdx + 1]).toMatch(/^\[Reply:/) - }) - - it('includes payload when present', () => { - const payload = '{"taskId":"task_1","exitCode":0}' - const banner = formatMessageBanner(makeMessage({ payload })) - expect(banner).toContain(`[Payload: ${payload}]`) - }) - - it('omits payload line when payload is null', () => { - const banner = formatMessageBanner(makeMessage({ payload: null })) - expect(banner).not.toContain('[Payload:') - }) - it('includes reply hint with message ID', () => { const banner = formatMessageBanner(makeMessage({ id: 'msg_xyz789' })) expect(banner).toContain( @@ -143,46 +106,4 @@ describe('formatMessageBanner', () => { expect(banner).not.toContain('[Reply:') } }) - - it('ends with a separator line', () => { - const banner = formatMessageBanner(makeMessage()) - const lines = banner.split('\n') - expect(lines.at(-1)).toMatch(/^─+$/) - }) -}) - -describe('formatMessagesForInjection', () => { - it('returns empty string for empty array', () => { - expect(formatMessagesForInjection([])).toBe('') - }) - - it('wraps multiple banners with orchestration messages header', () => { - const messages = [makeMessage({ id: 'msg_1' }), makeMessage({ id: 'msg_2' })] - const result = formatMessagesForInjection(messages) - expect(result).toContain('--- Orchestration Messages (2) ---') - expect(result).toContain('msg_1') - expect(result).toContain('msg_2') - expect(result).toMatch(/\n---\n$/) - }) -}) - -describe('formatMessagePointer', () => { - it('formats a singular pointer without message content', () => { - expect(formatMessagePointer(1, 'run:run_1')).toBe( - '\nYou have 1 orchestration message. Run `orca orchestration check --run run_1`.\n' - ) - }) - - it('pluralizes a batched pointer', () => { - expect(formatMessagePointer(3)).toContain('3 orchestration messages') - }) - - it('uses the terminal-resolved CLI command', () => { - expect(formatMessagePointer(1, 'run:run_wsl', 'orca-ide')).toContain( - '`orca-ide orchestration check --run run_wsl`' - ) - expect(formatMessagePointer(1, 'run:run_dev', 'orca-dev')).toContain( - '`orca-dev orchestration check --run run_dev`' - ) - }) }) diff --git a/src/main/ssh/ssh-remote-platform.test.ts b/src/main/ssh/ssh-remote-platform.test.ts index 0f1f5b3cb67..b55b6138f88 100644 --- a/src/main/ssh/ssh-remote-platform.test.ts +++ b/src/main/ssh/ssh-remote-platform.test.ts @@ -1,30 +1,7 @@ import { describe, expect, it } from 'vitest' -import { - assertSafeRemotePathSegment, - getRemoteHostPlatform, - joinRemotePath -} from './ssh-remote-platform' - -describe('joinRemotePath', () => { - it('joins POSIX remote paths', () => { - expect(joinRemotePath(getRemoteHostPlatform('linux-x64'), '/home/me', '.orca-remote')).toBe( - '/home/me/.orca-remote' - ) - }) - - it('normalizes and joins Windows remote paths with forward slashes for SFTP and Node', () => { - expect( - joinRemotePath(getRemoteHostPlatform('win32-x64'), 'C:\\Users\\me', '.orca-remote', 'relay') - ).toBe('C:/Users/me/.orca-remote/relay') - }) -}) +import { assertSafeRemotePathSegment } from './ssh-remote-platform' describe('assertSafeRemotePathSegment', () => { - it('accepts ordinary names under both path flavors', () => { - expect(() => assertSafeRemotePathSegment('report copy.txt', 'posix')).not.toThrow() - expect(() => assertSafeRemotePathSegment('report copy.txt', 'windows')).not.toThrow() - }) - it.each(['.', '..', '../secret', 'child/name', 'nul\0byte'])( 'rejects invalid segment %j under both path flavors', (segment) => { diff --git a/src/main/text-generation/conversation-name-generation-request.test.ts b/src/main/text-generation/conversation-name-generation-request.test.ts index a23811393c6..e05b731d706 100644 --- a/src/main/text-generation/conversation-name-generation-request.test.ts +++ b/src/main/text-generation/conversation-name-generation-request.test.ts @@ -78,21 +78,4 @@ describe('generateConversationName', () => { cancelLocalGeneration('conversation-name', '/repo') expect(await pending).toMatchObject({ success: false, canceled: true }) }) - - it('returns a sanitized name from the local generation lane', async () => { - const child = createMockDiscoveryChild() - const spawnAgent = vi.fn(() => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: The test child implements every process member the generation lane reads. - return child as unknown as SpawnedSourceControlAgentProcess - }) - const pending = generateConversationName({ - context: { firstPrompt: 'Fix login' }, - params: { agentId: 'custom', model: '', customAgentCommand: 'agent' }, - target: { kind: 'local', cwd: '/repo' }, - spawnAgent - }) - child.stdout.emit('data', Buffer.from('Title: "Fix login flow"\n')) - child.emit('close', 0) - expect(await pending).toEqual({ success: true, name: 'Fix login flow', agentLabel: 'agent' }) - }) }) diff --git a/src/renderer/src/lib/ios-web-platform.test.ts b/src/renderer/src/lib/ios-web-platform.test.ts deleted file mode 100644 index e5381b312ce..00000000000 --- a/src/renderer/src/lib/ios-web-platform.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { isIosWebPlatform } from './ios-web-platform' - -const IPAD_DESKTOP_MODE_UA = - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15' -const IPAD_MOBILE_MODE_UA = - 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/120.0.0.0 Mobile/15E148 Safari/604.1' -const IPHONE_UA = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1' -const IPOD_UA = - 'Mozilla/5.0 (iPod touch; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1' -const MAC_DESKTOP_UA = - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' -const MAC_ELECTRON_UA = - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) orca/1.0.0 Chrome/126.0.0.0 Electron/31.0.0 Safari/537.36' -const WINDOWS_UA = - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' -const ANDROID_UA = - 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36' - -describe('isIosWebPlatform', () => { - it('detects iPadOS desktop mode, which reports Macintosh and omits iPad', () => { - expect(isIosWebPlatform(IPAD_DESKTOP_MODE_UA, 5)).toBe(true) - }) - - it('detects iPad, iPhone and iPod mobile-mode user agents', () => { - expect(isIosWebPlatform(IPAD_MOBILE_MODE_UA, 5)).toBe(true) - expect(isIosWebPlatform(IPHONE_UA, 5)).toBe(true) - expect(isIosWebPlatform(IPOD_UA, 5)).toBe(true) - }) - - it('detects a mobile-mode iPad even when the touch count is unavailable', () => { - expect(isIosWebPlatform(IPAD_MOBILE_MODE_UA, 0)).toBe(true) - }) - - it('does not claim a Mac browser or the Electron app', () => { - expect(isIosWebPlatform(MAC_DESKTOP_UA, 0)).toBe(false) - expect(isIosWebPlatform(MAC_ELECTRON_UA, 0)).toBe(false) - }) - - it('does not claim a Mac whose touch peripheral reports a single point', () => { - expect(isIosWebPlatform(MAC_DESKTOP_UA, 1)).toBe(false) - }) - - it('does not claim Windows, Linux or Android', () => { - expect(isIosWebPlatform(WINDOWS_UA, 0)).toBe(false) - expect(isIosWebPlatform(ANDROID_UA, 5)).toBe(false) - }) -}) diff --git a/src/renderer/src/lib/order-empty-query-worktrees.test.ts b/src/renderer/src/lib/order-empty-query-worktrees.test.ts index 96683a276d4..97ac9c28cff 100644 --- a/src/renderer/src/lib/order-empty-query-worktrees.test.ts +++ b/src/renderer/src/lib/order-empty-query-worktrees.test.ts @@ -36,39 +36,6 @@ describe('orderEmptyQueryWorktrees', () => { expect(result.switchableWorktreesForRows.map((w) => w.id)).toEqual(['ssh', 'local']) }) - it('sorts visited worktrees by lastVisitedAt descending', () => { - const a = wt({ id: 'a', displayName: 'a' }) - const b = wt({ id: 'b', displayName: 'b' }) - const result = orderEmptyQueryWorktrees({ - visibleWorktrees: [a, b], - activeWorktreeId: null, - lastVisitedAtByWorktreeId: { a: 100, b: 200 } - }) - expect(result.switchableWorktreesForRows.map((w) => w.id)).toEqual(['b', 'a']) - }) - - it('falls back to lastActivityAt for never-visited worktrees', () => { - const a = wt({ id: 'a', displayName: 'a', lastActivityAt: 100 }) - const b = wt({ id: 'b', displayName: 'b', lastActivityAt: 200 }) - const result = orderEmptyQueryWorktrees({ - visibleWorktrees: [a, b], - activeWorktreeId: null, - lastVisitedAtByWorktreeId: {} - }) - expect(result.switchableWorktreesForRows.map((w) => w.id)).toEqual(['b', 'a']) - }) - - it('uses displayName as final tie-breaker', () => { - const a = wt({ id: 'a', displayName: 'apple', lastActivityAt: 100 }) - const b = wt({ id: 'b', displayName: 'banana', lastActivityAt: 100 }) - const result = orderEmptyQueryWorktrees({ - visibleWorktrees: [b, a], - activeWorktreeId: null, - lastVisitedAtByWorktreeId: {} - }) - expect(result.switchableWorktreesForRows.map((w) => w.id)).toEqual(['a', 'b']) - }) - it('does not crash the switcher when a visible worktree has no displayName (crash 99657ab1)', () => { // Persisted/discovered displayName can be undefined despite its type; raw localeCompare once crashed Cmd+J. const named = wt({ id: 'a', displayName: 'apple', lastActivityAt: 100 }) @@ -86,18 +53,6 @@ describe('orderEmptyQueryWorktrees', () => { ).not.toThrow() }) - it('excludes current worktree from rows but includes it in state list', () => { - const cur = wt({ id: 'cur', displayName: 'cur' }) - const other = wt({ id: 'other', displayName: 'other' }) - const result = orderEmptyQueryWorktrees({ - visibleWorktrees: [cur, other], - activeWorktreeId: 'cur', - lastVisitedAtByWorktreeId: {} - }) - expect(result.switchableWorktreesForRows.map((w) => w.id)).toEqual(['other']) - expect(result.visibleWorktreesForState.map((w) => w.id)).toEqual(['cur', 'other']) - }) - // Filter current workspace by host too or its same-ID SSH twin becomes unreachable. it('keeps the same-id worktree on the other host switchable', () => { const local = wt({ id: 'shared', displayName: 'local', hostId: 'local' }) @@ -110,15 +65,4 @@ describe('orderEmptyQueryWorktrees', () => { }) expect(result.switchableWorktreesForRows.map((w) => w.displayName)).toEqual(['ssh']) }) - - it('returns empty rows but non-empty state when only the current worktree is visible', () => { - const cur = wt({ id: 'cur', displayName: 'cur' }) - const result = orderEmptyQueryWorktrees({ - visibleWorktrees: [cur], - activeWorktreeId: 'cur', - lastVisitedAtByWorktreeId: {} - }) - expect(result.switchableWorktreesForRows).toEqual([]) - expect(result.visibleWorktreesForState).toHaveLength(1) - }) }) diff --git a/src/renderer/src/lib/unread-badge-count.test.ts b/src/renderer/src/lib/unread-badge-count.test.ts index b3e718b0f7e..5ce89965389 100644 --- a/src/renderer/src/lib/unread-badge-count.test.ts +++ b/src/renderer/src/lib/unread-badge-count.test.ts @@ -57,35 +57,12 @@ function count(overrides: Partial): number { } describe('getUnreadBadgeCount', () => { - it('counts unread worktrees', () => { - expect( - count({ - worktreesByRepo: { repo1: [worktree('wt-1'), worktree('wt-2', { isUnread: false })] } - }) - ).toBe(1) - }) - - it('skips archived worktrees, which the sidebar never shows', () => { - expect(count({ worktreesByRepo: { repo1: [worktree('wt-1', { isArchived: true })] } })).toBe(0) - }) - it('preserves id-only deduplication across execution hosts', () => { const rows = [worktree('wt-1', { hostId: 'local' }), worktree('wt-1', { hostId: 'ssh:remote' })] expect(count({ worktreesByRepo: { repo1: rows } })).toBe(1) expect(count({ worktreesByRepo: { repo1: rows }, visibleHostIds: new Set(['local']) })).toBe(1) }) - it('counts a row repeated across repo buckets once', () => { - expect( - count({ - worktreesByRepo: { - 'repo-a': [worktree('wt-1', { hostId: 'local' })], - 'repo-b': [worktree('wt-1', { hostId: 'local' })] - } - }) - ).toBe(1) - }) - it('counts unread folder workspaces alongside worktrees', () => { expect( count({ diff --git a/src/shared/ai-vault-scope-paths.test.ts b/src/shared/ai-vault-scope-paths.test.ts index b0c1748301e..c04d0d0b671 100644 --- a/src/shared/ai-vault-scope-paths.test.ts +++ b/src/shared/ai-vault-scope-paths.test.ts @@ -29,15 +29,6 @@ function makeWorktree(overrides: Partial = {}): Worktree { } describe('deriveAiVaultWorkspaceScopePaths', () => { - it('returns the active workspace path', () => { - const active = makeWorktree() - expect(deriveAiVaultWorkspaceScopePaths(active, [active])).toEqual(['/repo/orca']) - }) - - it('returns nothing without an active workspace', () => { - expect(deriveAiVaultWorkspaceScopePaths(null, [makeWorktree()])).toEqual([]) - }) - it('includes prior paths so renamed workspaces keep their transcripts', () => { const active = makeWorktree({ id: 'repo-1::/repo/orca-renamed', @@ -51,25 +42,6 @@ describe('deriveAiVaultWorkspaceScopePaths', () => { ]) }) - it('drops a prior path another live workspace now owns', () => { - // Sessions use cwd identity; claiming an occupied prior path would expose another workspace's transcripts. - const claimant = makeWorktree({ id: 'repo-1::/repo/orca', path: '/repo/orca' }) - const active = makeWorktree({ - id: 'repo-1::/repo/orca-renamed', - path: '/repo/orca-renamed', - priorWorktreeIds: ['repo-1::/repo/orca'] - }) - - expect(deriveAiVaultWorkspaceScopePaths(active, [claimant, active])).toEqual([ - '/repo/orca-renamed' - ]) - }) - - it('keeps a prior path the active workspace itself still owns', () => { - const active = makeWorktree({ priorWorktreeIds: ['repo-1::/repo/orca'] }) - expect(deriveAiVaultWorkspaceScopePaths(active, [active])).toEqual(['/repo/orca']) - }) - it('drops a claimed prior path regardless of where the claimant sits in the list', () => { // Exclude the active workspace before indexing owners so duplicates cannot hide later claimants. const active = makeWorktree({ @@ -93,27 +65,6 @@ describe('deriveAiVaultWorkspaceScopePaths', () => { } }) - it('derives workspace scope paths at scale', () => { - // The session-scope guard cannot catch quadratic deduplication confined to the workspace pass. - const prefix = '/Users/dev/orca/workspaces/orca-monorepo/feature-' - const worktrees = Array.from({ length: 1200 }, (_, i) => - makeWorktree({ id: `repo-1::${prefix}${i}`, path: `${prefix}${i}` }) - ) - const active = makeWorktree({ - id: `repo-1::${prefix}0`, - path: `${prefix}0`, - // Priors drive the claim check, which is the other per-call scan here. - priorWorktreeIds: Array.from({ length: 25 }, (_, i) => `repo-1::${prefix}prior-${i}`) - }) - - const startedAt = performance.now() - const paths = deriveAiVaultWorkspaceScopePaths(active, worktrees) - const elapsedMs = performance.now() - startedAt - - expect(paths).toHaveLength(1 + 25) - expect(elapsedMs).toBeLessThan(100) - }) - it('ignores prior ids belonging to another repo', () => { const active = makeWorktree({ priorWorktreeIds: ['repo-2::/repo/other'] }) expect(deriveAiVaultWorkspaceScopePaths(active, [active])).toEqual(['/repo/orca']) @@ -161,20 +112,4 @@ describe('deriveAiVaultScopeSessionPaths', () => { expect(deriveAiVaultScopeSessionPaths(active, [active, nfd])).toEqual([nfc]) }) - - it('derives scope paths at scale without re-normalizing the accumulator', () => { - // Match profile count/path lengths to catch quadratic normalization on workspace switches (~190ms before). - const prefix = '/Users/dev/orca/workspaces/orca-monorepo/feature-' - const worktrees = Array.from({ length: 1200 }, (_, i) => - makeWorktree({ id: `repo-1::${prefix}${i}`, path: `${prefix}${i}` }) - ) - - const startedAt = performance.now() - const paths = deriveAiVaultScopeSessionPaths(worktrees[0], worktrees) - const elapsedMs = performance.now() - startedAt - - expect(paths).toHaveLength(worktrees.length) - // Allow slow CI hosts while catching the previous ~190ms cost (now ~1ms). - expect(elapsedMs).toBeLessThan(100) - }) }) diff --git a/src/shared/secure-path-hardening-retry-budget.test.ts b/src/shared/secure-path-hardening-retry-budget.test.ts index 0008643680c..7d07fc2a989 100644 --- a/src/shared/secure-path-hardening-retry-budget.test.ts +++ b/src/shared/secure-path-hardening-retry-budget.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { configureHardeningRetryBudget, - hardeningRetryDelayMs, mayAttemptHardening, recordHardeningOutcome } from './secure-path-hardening-retry-budget' @@ -55,28 +54,6 @@ describe('secure path hardening retry budget', () => { return attemptedAt } - it('doubles the delay after each consecutive failure, up to the ceiling', () => { - expect(hardeningRetryDelayMs(1)).toBe(1 * MINUTE) - expect(hardeningRetryDelayMs(2)).toBe(2 * MINUTE) - expect(hardeningRetryDelayMs(3)).toBe(4 * MINUTE) - expect(hardeningRetryDelayMs(4)).toBe(8 * MINUTE) - expect(hardeningRetryDelayMs(5)).toBe(16 * MINUTE) - expect(hardeningRetryDelayMs(6)).toBe(30 * MINUTE) - expect(hardeningRetryDelayMs(50)).toBe(30 * MINUTE) - expect(hardeningRetryDelayMs(5000)).toBe(30 * MINUTE) - }) - - it('allows the first attempt for a path it has never seen', () => { - expect(mayAttemptHardening(PATH)).toBe(true) - }) - - // The #4901 condition: the env store re-hardens on the read path about twice a second. - it('collapses a read-path poll to a single attempt in the first minute', () => { - const attemptedAt = pollUntil(55_000, 500) - - expect(attemptedAt).toEqual([0]) - }) - it('re-probes on the documented curve rather than on every read', () => { // Six hours of polling every 30s: 720 reads, and only the backoff decides how many run. const attemptedAt = pollUntil(6 * 60 * MINUTE, 30_000) @@ -88,14 +65,6 @@ describe('secure path hardening retry budget', () => { expect(trailingGaps.slice(0, -1)).toEqual([30, 30, 30]) }) - // The failure this replaced: three transient failures used to disable a path until restart. - it('never abandons a path, however long it has been failing', () => { - pollUntil(30 * 24 * 60 * MINUTE, 15 * MINUTE) - - clock += 31 * MINUTE - expect(mayAttemptHardening(PATH)).toBe(true) - }) - /** Backwards wall-clock steps previously held paths below retry delay until time caught up; monotonic elapsed time must keep probing. */ it('re-probes after a backwards clock step rather than waiting for the wall clock', () => { pollUntil(6 * 60 * MINUTE, 30_000) diff --git a/src/shared/source-control-discard-all-sequence.test.ts b/src/shared/source-control-discard-all-sequence.test.ts index 58ad9e6c757..7d448b89510 100644 --- a/src/shared/source-control-discard-all-sequence.test.ts +++ b/src/shared/source-control-discard-all-sequence.test.ts @@ -4,7 +4,6 @@ import { getStageAllPaths, getUnstageAllPaths, isStageableStatusEntry, - isSubmoduleWorktreeOnlyChange, runDiscardAllForArea } from './source-control-discard-all-sequence' import type { GitStatusEntry } from './git-status-types' @@ -18,17 +17,6 @@ function entry(partial: Partial & { path: string }): GitStatusEn } describe('getDiscardAllPaths', () => { - it('returns only paths in the requested area', () => { - const entries: GitStatusEntry[] = [ - entry({ path: 'a.ts', area: 'staged' }), - entry({ path: 'b.ts', area: 'unstaged' }), - entry({ path: 'c.ts', area: 'untracked', status: 'untracked' }) - ] - expect(getDiscardAllPaths(entries, 'staged')).toEqual(['a.ts']) - expect(getDiscardAllPaths(entries, 'unstaged')).toEqual(['b.ts', 'c.ts']) - expect(getDiscardAllPaths(entries, 'untracked')).toEqual(['c.ts']) - }) - it('skips entries with an unresolved conflict', () => { const entries: GitStatusEntry[] = [ entry({ path: 'clean.ts', area: 'unstaged' }), @@ -56,24 +44,9 @@ describe('getDiscardAllPaths', () => { // Bulk discard must preserve local resolutions or users would have to resolve them again. expect(getDiscardAllPaths(entries, 'unstaged')).toEqual(['clean.ts']) }) - - it('returns an empty array when nothing matches', () => { - expect(getDiscardAllPaths([], 'staged')).toEqual([]) - expect(getDiscardAllPaths([entry({ path: 'a.ts', area: 'staged' })], 'unstaged')).toEqual([]) - }) }) describe('getStageAllPaths', () => { - it('returns only paths in the requested area', () => { - const entries: GitStatusEntry[] = [ - entry({ path: 'a.ts', area: 'staged' }), - entry({ path: 'b.ts', area: 'unstaged' }), - entry({ path: 'c.ts', area: 'untracked', status: 'untracked' }) - ] - expect(getStageAllPaths(entries, 'unstaged')).toEqual(['b.ts']) - expect(getStageAllPaths(entries, 'untracked')).toEqual(['c.ts']) - }) - it('skips entries with an unresolved conflict', () => { const entries: GitStatusEntry[] = [ entry({ path: 'clean.ts', area: 'unstaged' }), @@ -117,36 +90,9 @@ describe('getStageAllPaths', () => { ] expect(getStageAllPaths(entries, 'unstaged')).toEqual(['changed-gitlink']) }) - - it('returns an empty array when nothing matches', () => { - expect(getStageAllPaths([], 'unstaged')).toEqual([]) - expect(getStageAllPaths([entry({ path: 'a.ts', area: 'staged' })], 'unstaged')).toEqual([]) - }) }) describe('status entry stageability', () => { - it('marks nested-only submodule changes as not stageable from the parent repo', () => { - const nestedOnly = entry({ - path: 'nested-repo', - area: 'unstaged', - submodule: { commitChanged: false, trackedChanges: true, untrackedChanges: false } - }) - - expect(isSubmoduleWorktreeOnlyChange(nestedOnly)).toBe(true) - expect(isStageableStatusEntry(nestedOnly)).toBe(false) - }) - - it('keeps changed submodule gitlinks stageable from the parent repo', () => { - const changedGitlink = entry({ - path: 'nested-repo', - area: 'unstaged', - submodule: { commitChanged: true, trackedChanges: true, untrackedChanges: true } - }) - - expect(isSubmoduleWorktreeOnlyChange(changedGitlink)).toBe(false) - expect(isStageableStatusEntry(changedGitlink)).toBe(true) - }) - it('marks lazily-expanded submodule-internal entries as not stageable', () => { const innerEntry = entry({ path: 'flutter_mine/lib/main.dart', @@ -161,15 +107,6 @@ describe('status entry stageability', () => { }) describe('getUnstageAllPaths', () => { - it('returns only staged-area paths', () => { - const entries: GitStatusEntry[] = [ - entry({ path: 'a.ts', area: 'staged' }), - entry({ path: 'b.ts', area: 'unstaged' }), - entry({ path: 'c.ts', area: 'untracked', status: 'untracked' }) - ] - expect(getUnstageAllPaths(entries)).toEqual(['a.ts']) - }) - it('includes staged conflict rows', () => { const entries: GitStatusEntry[] = [ entry({ path: 'clean.ts', area: 'staged' }), @@ -189,11 +126,6 @@ describe('getUnstageAllPaths', () => { // Unstaging conflicts safely mirrors the per-row action; no conflict filtering is needed. expect(getUnstageAllPaths(entries)).toEqual(['clean.ts', 'conflict.ts', 'resolved.ts']) }) - - it('returns an empty array when nothing is staged', () => { - expect(getUnstageAllPaths([])).toEqual([]) - expect(getUnstageAllPaths([entry({ path: 'a.ts', area: 'unstaged' })])).toEqual([]) - }) }) describe('runDiscardAllForArea', () => { @@ -248,30 +180,6 @@ describe('runDiscardAllForArea', () => { } } - it('no-ops when the path list is empty', async () => { - const ctx = makeDeps() - const result = await runDiscardAllForArea('staged', [], ctx.deps) - expect(result).toEqual({ discarded: [], failed: [], aborted: false }) - expect(ctx.bulkUnstage).not.toHaveBeenCalled() - expect(ctx.discardOne).not.toHaveBeenCalled() - }) - - it('discards unstaged paths one-by-one without bulk-unstaging', async () => { - const ctx = makeDeps() - const result = await runDiscardAllForArea('unstaged', ['a.ts', 'b.ts'], ctx.deps) - expect(result).toEqual({ discarded: ['a.ts', 'b.ts'], failed: [], aborted: false }) - expect(ctx.bulkUnstage).not.toHaveBeenCalled() - expect(ctx.discardOneCalls).toEqual(['a.ts', 'b.ts']) - }) - - it('discards untracked paths one-by-one without bulk-unstaging', async () => { - const ctx = makeDeps() - const result = await runDiscardAllForArea('untracked', ['new.ts'], ctx.deps) - expect(result).toEqual({ discarded: ['new.ts'], failed: [], aborted: false }) - expect(ctx.bulkUnstage).not.toHaveBeenCalled() - expect(ctx.discardOneCalls).toEqual(['new.ts']) - }) - it('bulk-unstages staged paths before the per-file discard loop', async () => { const ctx = makeDeps() const result = await runDiscardAllForArea('staged', ['a.ts', 'b.ts'], ctx.deps) @@ -284,14 +192,6 @@ describe('runDiscardAllForArea', () => { ) }) - it('uses bulk discard when the dependency is available', async () => { - const ctx = makeDeps() - const result = await runDiscardAllForArea('unstaged', ['a.ts', 'b.ts'], ctx.depsWithBulkDiscard) - expect(result).toEqual({ discarded: ['a.ts', 'b.ts'], failed: [], aborted: false }) - expect(ctx.discardManyCalls).toEqual([['a.ts', 'b.ts']]) - expect(ctx.discardOne).not.toHaveBeenCalled() - }) - it('bulk-unstages staged paths before bulk discard', async () => { const ctx = makeDeps() const result = await runDiscardAllForArea('staged', ['a.ts', 'b.ts'], ctx.depsWithBulkDiscard) @@ -339,10 +239,4 @@ describe('runDiscardAllForArea', () => { // Reserve aborted for failed bulk unstage so callers can distinguish no work from partial success. expect(ctx.errors).toEqual([error]) }) - - it('does not invoke the error callback on a happy-path staged run', async () => { - const ctx = makeDeps() - await runDiscardAllForArea('staged', ['a.ts'], ctx.deps) - expect(ctx.onError).not.toHaveBeenCalled() - }) }) diff --git a/src/shared/source-control-section-order.test.ts b/src/shared/source-control-section-order.test.ts index 3a2093367f0..62e4e3a2aa4 100644 --- a/src/shared/source-control-section-order.test.ts +++ b/src/shared/source-control-section-order.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest' import type { GitStatusEntry } from './git-status-types' import { buildSourceControlDisplaySections, - getConflictReviewEntries, getSourceControlSectionViewAction, resolveSourceControlGroupOrder, splitPinnedSourceControlConflicts, @@ -26,35 +25,7 @@ function groups(partial: Partial): SourceControlEntryG } } -describe('resolveSourceControlGroupOrder', () => { - it('keeps Changes first by default', () => { - expect(resolveSourceControlGroupOrder(undefined)).toEqual(['unstaged', 'staged']) - }) - - it('supports staged-first and untracked-first presets', () => { - expect(resolveSourceControlGroupOrder('staged-first')).toEqual(['staged', 'unstaged']) - expect(resolveSourceControlGroupOrder('untracked-first')).toEqual(['unstaged', 'staged']) - }) -}) - describe('buildSourceControlDisplaySections', () => { - it('uses the configured order for normal sections', () => { - const sections = buildSourceControlDisplaySections( - groups({ - staged: [entry({ area: 'staged', path: 'staged.ts' })], - unstaged: [entry({ area: 'unstaged', path: 'changed.ts' })], - untracked: [entry({ area: 'untracked', path: 'new.ts', status: 'untracked' })] - }), - resolveSourceControlGroupOrder('staged-first') - ) - - expect(sections.map((section) => section.id)).toEqual(['staged', 'unstaged']) - expect(sections[1]?.items.map(({ path, area }) => ({ path, area }))).toEqual([ - { path: 'changed.ts', area: 'unstaged' }, - { path: 'new.ts', area: 'untracked' } - ]) - }) - it('combines working files in path order before staged changes by default', () => { const sections = buildSourceControlDisplaySections( groups({ @@ -69,27 +40,6 @@ describe('buildSourceControlDisplaySections', () => { expect(sections[0]?.items.map((item) => item.path)).toEqual(['new.ts', 'working.ts']) }) - it('keeps conflicts pinned before the configured normal order', () => { - const sections = buildSourceControlDisplaySections( - groups({ - staged: [entry({ area: 'staged', path: 'staged.ts' })], - unstaged: [ - entry({ - area: 'unstaged', - path: 'conflict.ts', - conflictKind: 'both_modified', - conflictStatus: 'unresolved' - }), - entry({ area: 'unstaged', path: 'changed.ts' }) - ], - untracked: [entry({ area: 'untracked', path: 'new.ts', status: 'untracked' })] - }), - resolveSourceControlGroupOrder('staged-first') - ) - - expect(sections.map((section) => section.id)).toEqual(['conflicts', 'staged', 'unstaged']) - }) - it('pins conflict rows and removes them from the normal Changes section', () => { const unresolved = entry({ area: 'unstaged', @@ -140,25 +90,6 @@ describe('buildSourceControlDisplaySections', () => { expect(sections[1]?.items).toEqual([staged]) }) - it('builds review entries only for unresolved conflicts', () => { - expect( - getConflictReviewEntries([ - entry({ - area: 'unstaged', - path: 'conflict.ts', - conflictKind: 'both_modified', - conflictStatus: 'unresolved' - }), - entry({ - area: 'unstaged', - path: 'resolved.ts', - conflictKind: 'both_modified', - conflictStatus: 'resolved_locally' - }) - ]) - ).toEqual([{ path: 'conflict.ts', conflictKind: 'both_modified' }]) - }) - it('routes the pinned Conflicts section to conflict review', () => { const sections = buildSourceControlDisplaySections( groups({ @@ -186,26 +117,6 @@ describe('buildSourceControlDisplaySections', () => { }) }) - it('scopes normal combined-diff actions to the conflict-split section items', () => { - const pinned = entry({ - area: 'unstaged', - path: 'resolved.ts', - conflictKind: 'both_modified', - conflictStatus: 'resolved_locally' - }) - const normal = entry({ area: 'unstaged', path: 'normal.ts' }) - const sections = buildSourceControlDisplaySections( - groups({ unstaged: [pinned, normal] }), - resolveSourceControlGroupOrder('changes-first') - ) - - expect(getSourceControlSectionViewAction(sections[1]!)).toEqual({ - kind: 'combined-diff', - area: 'unstaged', - entries: [normal] - }) - }) - it('routes locally resolved-only conflict sections to combined diff', () => { const resolved = entry({ area: 'unstaged', diff --git a/src/shared/structured-agent-session-composer.test.ts b/src/shared/structured-agent-session-composer.test.ts index d3585d6bbf4..03cc4e7b19b 100644 --- a/src/shared/structured-agent-session-composer.test.ts +++ b/src/shared/structured-agent-session-composer.test.ts @@ -2,80 +2,10 @@ import { describe, expect, it, vi } from 'vitest' import { dispatchStructuredAgentSessionComposerCommand, isStructuredAgentSessionComposerCommand, - structuredAgentSessionGoalObjective, structuredSessionSlashCommands, structuredSlashCommands } from './structured-agent-session-composer' -describe('structuredSlashCommands', () => { - const hostController = { - snapshot: [], - invokeAction: async () => true, - setOption: async () => true, - conversationCommands: ['clear', 'compact'] as const, - runConversationCommand: async () => ({ accepted: true, error: null }) - } - const REFUSAL = /is not available in chat sessions/ - - // Menu and dispatcher must share policy or Codex-only tokens can reach Claude as literal prose instead of being handled. - it.each(['codex', 'claude'] as const)( - 'offers %s only commands the host answers or the agent runs', - async (agent) => { - const offered = structuredSlashCommands(['clear', 'compact'], agent) - expect(offered.length).toBeGreaterThan(0) - for (const command of offered) { - const outcome = await dispatchStructuredAgentSessionComposerCommand(`/${command.name}`, { - ...hostController, - agent - }) - expect(outcome.error ?? '').not.toMatch(REFUSAL) - } - } - ) - - // Codex reports no catalog; fallback rows make its working text-driven commands discoverable. - it('offers Codex the /goal the model acts on, described from the catalog', () => { - const offered = structuredSlashCommands(['clear', 'compact'], 'codex') - expect(offered.map((command) => command.name)).toEqual([ - 'model', - 'effort', - 'clear', - 'compact', - 'goal' - ]) - expect(offered.find((command) => command.name === 'goal')?.description).toBe( - 'Set or view the goal' - ) - expect(isStructuredAgentSessionComposerCommand('/goal', 'codex')).toBe(false) - }) - - it('adds nothing for an agent whose own harness expands its commands', () => { - expect(structuredSlashCommands(['clear', 'compact'], 'claude').map((c) => c.name)).toEqual([ - 'model', - 'effort', - 'clear', - 'compact' - ]) - }) - - it('offers only the commands a chat session can carry out', () => { - expect(structuredSlashCommands().map((command) => command.name)).toEqual(['model', 'effort']) - }) - it('adds only implemented host-supported conversation commands', () => { - expect(structuredSlashCommands(['clear', 'compact']).map((command) => command.name)).toEqual([ - 'model', - 'effort', - 'clear', - 'compact' - ]) - expect(structuredSlashCommands(['compact']).map((command) => command.name)).toEqual([ - 'model', - 'effort', - 'compact' - ]) - }) -}) - describe('structuredSessionSlashCommands', () => { const names = (commands: readonly { name: string }[]) => commands.map(({ name }) => name) @@ -111,22 +41,6 @@ describe('structuredSessionSlashCommands', () => { }) }) -describe('isStructuredAgentSessionComposerCommand', () => { - // Claim typed TUI-only commands even when hidden from the menu, so they do not reach the model as prose. - it.each([ - ['codex', 'vim'], - ['codex', 'clear'], - ['claude', 'compact'], - ['claude', 'clear'] - ] as const)('claims the unoffered %s command /%s', (agent, name) => { - expect(isStructuredAgentSessionComposerCommand(`/${name}`, agent)).toBe(true) - }) - - it('leaves an unknown token to the chat path', () => { - expect(isStructuredAgentSessionComposerCommand('/my-skill', 'claude')).toBe(false) - }) -}) - describe('dispatchStructuredAgentSessionComposerCommand', () => { const controller = { agent: 'codex' as const, @@ -134,14 +48,6 @@ describe('dispatchStructuredAgentSessionComposerCommand', () => { invokeAction: async () => true, setOption: async () => true } - - it('names what does work when a TUI-only command is typed', async () => { - const outcome = await dispatchStructuredAgentSessionComposerCommand('/vim', controller) - expect(outcome.handled).toBe(true) - expect(outcome.error).toBe( - '/vim is not available in chat sessions. Use the slash menu to see available commands.' - ) - }) it.each(['claude', 'codex'] as const)( 'handles %s conversation commands without message fallthrough', async (agent) => { @@ -196,21 +102,6 @@ describe('agent-implemented commands pass through to the agent', () => { } ) - it.each(['clear', 'compact', 'model', 'effort'] as const)( - 'still claims the host-owned /%s on Claude', - async (name) => { - expect(isStructuredAgentSessionComposerCommand(`/${name}`, 'claude')).toBe(true) - expect( - ( - await dispatchStructuredAgentSessionComposerCommand(`/${name}`, { - ...controller, - agent: 'claude' - }) - ).handled - ).toBe(true) - } - ) - // Codex app-server lacks a slash parser, but its model handles /goal through goal tools. it('passes /goal through on Codex, arguments and all', async () => { expect(isStructuredAgentSessionComposerCommand('/goal', 'codex')).toBe(false) @@ -244,14 +135,6 @@ describe('agent-implemented commands pass through to the agent', () => { ).toEqual({ handled: true, accepted: false, error: null }) }) - it('reads the objective a goal-mode draft names, with or without a typed /goal', () => { - expect(structuredAgentSessionGoalObjective(' Ship the parser ')).toBe('Ship the parser') - expect(structuredAgentSessionGoalObjective('/goal Ship the parser ')).toBe('Ship the parser') - expect(structuredAgentSessionGoalObjective('/GOAL')).toBe('') - // Goal mode treats non-goal commands as objective text. - expect(structuredAgentSessionGoalObjective('/model gpt-5')).toBe('/model gpt-5') - }) - it('asks for an objective when a goal-capable host gets a bare /goal', async () => { const setThreadGoalObjective = vi.fn(async () => true) expect( diff --git a/src/shared/structured-agent-session-live-turn.test.ts b/src/shared/structured-agent-session-live-turn.test.ts index 70fd1f27c6c..7043ef480e0 100644 --- a/src/shared/structured-agent-session-live-turn.test.ts +++ b/src/shared/structured-agent-session-live-turn.test.ts @@ -26,10 +26,6 @@ describe('isStructuredAgentSessionThinking', () => { blocks: [{ type: 'text', text: 'Weighing two approaches' }] }) - it('is true while reasoning is the newest thing the turn produced', () => { - expect(isStructuredAgentSessionThinking({ items: [turnStart, reasoning(2)] })).toBe(true) - }) - it("reads a reasoning row's own state when its host keeps one", () => { const withState = (state: 'running' | 'completed'): AgentJournalRenderItem => item('reasoning-state', 2, { @@ -64,21 +60,6 @@ describe('isStructuredAgentSessionThinking', () => { ).toBe(false) }) - it('is false when a turn produced no reasoning at all', () => { - expect(isStructuredAgentSessionThinking({ items: [turnStart] })).toBe(false) - expect(isStructuredAgentSessionThinking({ items: [] })).toBe(false) - }) - - it('does not read an earlier turn as this one reasoning', () => { - // A new turn with no content must not inherit the previous turn’s reasoning. - const newTurn = item('turn-2-start', 2, { - kind: 'status', - text: 'Working', - turnLifecycle: { turnId: 'turn-2', state: 'running' } - }) - expect(isStructuredAgentSessionThinking({ items: [reasoning(1), newTurn] })).toBe(false) - }) - it('does not read a completed turn as reasoning during the next pending dispatch', () => { const completedTurn = item('turn-1', 1, { kind: 'turn', @@ -191,17 +172,6 @@ describe("the live-turn readers answer for the session's own agent", () => { ).toBe(false) }) - it('still reports the parent as thinking when the parent itself is reasoning', () => { - const ownReasoning = item('own-reasoning', 3, { - kind: 'message', - role: 'reasoning', - blocks: [{ type: 'text', text: 'Weighing two approaches' }] - }) - expect(isStructuredAgentSessionThinking({ items: [turnStart, spawnCall, ownReasoning] })).toBe( - true - ) - }) - it("reports the parent's own running call while a subagent runs its own", () => { const childCall = child('child-grep', 3, { kind: 'tool-call', @@ -236,16 +206,4 @@ describe("the live-turn readers answer for the session's own agent", () => { 'Task' ) }) - - it("reads a row written before linkage existed as the parent's own", () => { - const legacyChildCall = item('legacy-call', 3, { - kind: 'tool-call', - name: 'Grep', - input: { pattern: 'x' }, - state: 'running' - }) - expect( - statusStructuredAgentSessionToolCall([turnStart, spawnCall, legacyChildCall])?.name - ).toBe('Grep') - }) }) diff --git a/src/shared/structured-agent-session-model-catalog-application.test.ts b/src/shared/structured-agent-session-model-catalog-application.test.ts index a20f63e83e3..3c7ba7cbd22 100644 --- a/src/shared/structured-agent-session-model-catalog-application.test.ts +++ b/src/shared/structured-agent-session-model-catalog-application.test.ts @@ -7,7 +7,6 @@ import { applyStructuredAgentSessionModelCatalog, applyStructuredAgentSessionOptions, createStructuredAgentSessionOptionState, - settleStructuredAgentSessionBuiltinCatalog, structuredAgentSessionOptionSnapshot, canSetStructuredAgentSessionOption, commitStructuredAgentSessionOption @@ -36,29 +35,6 @@ const HOST_CATALOG: AgentSessionModelCatalogResult = { const LAUNCH = { newLaunch: true } describe('structured option state from the host model catalog', () => { - it('renders the quiet placeholder before the host answers, never a built-in label', () => { - const state = createStructuredAgentSessionOptionState('codex', SEED) - expect(structuredAgentSessionOptionSnapshot(state)).toEqual([ - expect.objectContaining({ - id: 'model', - valueSource: 'unknown', - settable: false, - kind: { type: 'select', choices: [] } - }) - ]) - }) - - it('makes the built-in list pickable, naming nothing, once the host says it has none', () => { - const state = settleStructuredAgentSessionBuiltinCatalog( - createStructuredAgentSessionOptionState('codex', SEED) - ) - expect(state.catalogSource).toBe('builtin') - const model = structuredAgentSessionOptionSnapshot(state).find((d) => d.id === 'model')! - expect(model.kind.type === 'select' && model.kind.choices.length).toBeGreaterThan(0) - expect(model.settable).toBe(true) - expect(model.kind.type === 'select' ? model.kind.currentValue : null).toBeUndefined() - }) - it('upgrades the seed with host models as a provisional, uncommitted default', () => { const state = applyStructuredAgentSessionModelCatalog( createStructuredAgentSessionOptionState('codex', SEED), @@ -154,58 +130,6 @@ describe('structured option state from the host model catalog', () => { ).toBe(hosted) }) - it('refreshes inventory after a live answer while retaining the chat’s own selection', () => { - const live = applyStructuredAgentSessionOptions( - createStructuredAgentSessionOptionState('codex', SEED), - SEED, - { - models: [{ id: 'gpt-live', label: 'GPT Live', isDefault: true, efforts: [] }], - current: { model: 'gpt-live', confirmed: ['model'] } - } - ) - expect(live.catalogSource).toBe('live') - const refreshed = applyStructuredAgentSessionModelCatalog(live, SEED, HOST_CATALOG, { - ...LAUNCH, - coherentCatalog: true - }) - for (const agent of ['grok', 'pi']) { - const sourced = { ...live, record: { ...live.record, agent } } - expect(applyStructuredAgentSessionModelCatalog(sourced, SEED, HOST_CATALOG, LAUNCH)).toBe( - sourced - ) - } - expect(refreshed.catalog?.models.map((row) => row.id)).toEqual(['gpt-hosted']) - expect(refreshed.record).toBe(live.record) - const snapshot = structuredAgentSessionOptionSnapshot(live) - const model = snapshot.find((descriptor) => descriptor.id === 'model')! - expect(model.kind.type === 'select' ? model.kind.currentValue : null).toBe('gpt-live') - }) - - it('shows Pi live choices immediately and keeps them after a late, narrower host list', () => { - const seed = structuredAgentSessionSeedCatalog('pi')! - const models = [ - { id: 'A', label: 'A', isDefault: true, efforts: [] }, - { id: 'B', label: 'B', isDefault: false, efforts: [] } - ] - const launch = { ...LAUNCH, coherentCatalog: true } - const live = applyStructuredAgentSessionOptions( - createStructuredAgentSessionOptionState('pi', seed), - seed, - { models, current: { model: 'A', defaulted: [] } }, - launch - ) - const choices = (state: typeof live) => structuredAgentSessionOptionSnapshot(state)[0]?.kind - expect(choices(live)).toMatchObject({ choices: [{ value: 'A' }, { value: 'B' }] }) - const settled = applyStructuredAgentSessionModelCatalog( - live, - seed, - { origin: 'probe', models: [models[0]!], fetchedAt: 1 }, - launch - ) - expect(choices(settled)).toEqual(choices(live)) - expect(canSetStructuredAgentSessionOption(settled, 'model', 'B')).toBe(true) - }) - it.each([ ['codex', false], ['pi', true] diff --git a/src/shared/structured-agent-session-queued-cards.test.ts b/src/shared/structured-agent-session-queued-cards.test.ts index d62ead6a271..6c9927b2bd1 100644 --- a/src/shared/structured-agent-session-queued-cards.test.ts +++ b/src/shared/structured-agent-session-queued-cards.test.ts @@ -4,7 +4,6 @@ import type { AgentSessionQueuedMessage } from './agent-session-wire' import { newestSteerableQueuedMessageCard, projectQueuedMessageCards, - queuedMessageCardSteers, queuedMessagesQueuePause } from './structured-agent-session-queued-cards' @@ -51,16 +50,6 @@ function handOff( const IDLE = {} describe('queued message cards', () => { - it('orders by host position whatever order the list arrives in', () => { - const cards = projectQueuedMessageCards([draft('b', 2), draft('a', 1), draft('c', 3)], [], IDLE) - expect(cards.map((card) => card.messageId)).toEqual(['a', 'b', 'c']) - expect(cards[0]?.text).toBe('text of a') - }) - - it('holds: a waiting card waits its turn', () => { - expect(projectQueuedMessageCards([draft('a', 1)], [], IDLE)[0]?.hold).toBe('turn') - }) - it('a returned card carries its stored reason and blocks the label of drafts behind it', () => { const cards = projectQueuedMessageCards( [ @@ -83,15 +72,6 @@ describe('queued message cards', () => { expect(cards[1]?.hold).toBe('behind-returned') }) - it('a paused draft says so, carrying the host marker for the caption to localize', () => { - const cards = projectQueuedMessageCards( - [draft('a', 1, { paused: true, pausedReason: 'send_failed' })], - [], - IDLE - ) - expect(cards[0]).toMatchObject({ hold: 'paused', pausedReason: 'send_failed' }) - }) - it('shows a draft a Stop put back: its rejected hand-off is what sent it back', () => { // The Stop pause belongs to the queue, so the draft itself carries no hold. const requeued = draft('requeued', 1) @@ -147,15 +127,6 @@ describe('queued message cards', () => { ]) }) - it('a paused queue holds every waiting card, in order', () => { - const cards = projectQueuedMessageCards([draft('held', 1), draft('typed-after', 2)], [], { - queuePaused: true - }) - expect(cards.map((card) => card.hold)).toEqual(['queue-paused', 'queue-paused']) - // Still Steer: the header row, not the card, says it waits. - expect(cards.map((card) => queuedMessageCardSteers(card))).toEqual([true, true]) - }) - it("the header names the queue's pause while it holds a card, and none over cards Resume would not send", () => { const stopped = { reason: 'stopped' } as const const project = (messages: AgentSessionQueuedMessage[], queuePaused = true) => @@ -187,12 +158,6 @@ describe('queued message cards', () => { expect(cards.map((card) => card.hold)).toEqual(['paused', 'turn', 'paused', 'turn']) }) - it('steers the newest card', () => { - const cards = projectQueuedMessageCards([draft('a', 1), draft('b', 2)], [], IDLE) - expect(newestSteerableQueuedMessageCard(cards)?.messageId).toBe('b') - expect(newestSteerableQueuedMessageCard([])).toBeNull() - }) - it('marks a command card, which the chord never steers', () => { const compact: AgentSessionQueuedMessage = { ...draft('c', 2), @@ -218,25 +183,3 @@ describe('queued message cards', () => { expect(cards[1]).not.toHaveProperty('waitsForAgent') }) }) - -describe("another agent's card", () => { - it('carries who it is from, read through the shared reader', () => { - const from = { - kind: 'agent' as const, - senders: [ - { - party: { address: 'term_a', terminalHandle: 'term_a', orcaSessionId: null }, - name: 'Coder' - } - ], - orchestration: null - } - const agentDraft = draft('a', 1) - const cards = projectQueuedMessageCards( - [{ ...agentDraft, body: { ...agentDraft.body, from } }, draft('b', 2)], - [], - IDLE - ) - expect(cards.map((card) => card.from)).toEqual([from, undefined]) - }) -}) diff --git a/src/shared/terminal-composer-draft.test.ts b/src/shared/terminal-composer-draft.test.ts index b3d27df347d..31e5a76087b 100644 --- a/src/shared/terminal-composer-draft.test.ts +++ b/src/shared/terminal-composer-draft.test.ts @@ -5,47 +5,6 @@ import { } from './terminal-composer-draft' describe('detectTerminalComposerDraft', () => { - it('separates a cursor-right suggestion from the composer line', () => { - expect( - detectTerminalComposerDraft({ - rows: ['────────', '❯ proceed with the release'], - typedRows: ['────────', '❯'], - promptGlyphBoldRows: [false, false], - rowsBelow: [], - typedRowsBelow: [], - beforeCursor: '❯ ', - afterCursor: '', - rawAfterCursor: 'proceed with the release', - cursorHidden: false, - cursorViewportRow: 8 - }) - ).toEqual({ - text: 'proceed with the release', - promptRow: 8, - cursorRow: 8, - endRow: 8, - promptGlyph: '❯' - }) - }) - - it('keeps stock dim placeholders out of draft metadata', () => { - const context = { - rows: ['› Ask Codex to do anything'], - typedRows: ['›'], - promptGlyphBoldRows: [true], - rowsBelow: ['', 'gpt-5.6 · ~/repo'], - typedRowsBelow: ['', 'gpt-5.6 · ~/repo'], - beforeCursor: '› ', - afterCursor: '', - rawAfterCursor: 'Ask Codex to do anything', - cursorHidden: false, - cursorViewportRow: 4 - } - - expect(detectTerminalComposerDraft(context)).toBeNull() - expect(hasTerminalComposerPlaceholder(context)).toBe(true) - }) - it('keeps a typed draft whose stock placeholder is still rendered to its right', () => { // Repaints can leave the placeholder beside typed text; do not mask that user draft. const context = { @@ -71,23 +30,6 @@ describe('detectTerminalComposerDraft', () => { expect(hasTerminalComposerPlaceholder(context)).toBe(false) }) - it('does not duplicate real typed text left of the cursor', () => { - expect( - detectTerminalComposerDraft({ - rows: ['────────', '❯ review the change'], - typedRows: ['────────', '❯ review the change'], - promptGlyphBoldRows: [false, false], - rowsBelow: [], - typedRowsBelow: [], - beforeCursor: '❯ review the change', - afterCursor: '', - rawAfterCursor: '', - cursorHidden: false, - cursorViewportRow: 2 - })?.text - ).toBe('review the change') - }) - it('preserves a shell prompt that happens to use the Claude glyph', () => { expect( detectTerminalComposerDraft({ @@ -145,58 +87,6 @@ describe('detectTerminalComposerDraft', () => { }) }) - it('recognizes the bold Codex ultra composer glyph', () => { - expect( - detectTerminalComposerDraft({ - rows: ['» review the change'], - typedRows: ['» review the change'], - promptGlyphBoldRows: [true], - rowsBelow: ['gpt-5.6 · ~/repo'], - typedRowsBelow: ['gpt-5.6 · ~/repo'], - beforeCursor: '» review the change', - afterCursor: '', - rawAfterCursor: '', - cursorHidden: false, - cursorViewportRow: 4 - }) - ).toMatchObject({ text: 'review the change', promptGlyph: '»' }) - }) - - it('recognizes the bold Codex composer when its footer is visible', () => { - expect( - detectTerminalComposerDraft({ - rows: ['› review the change'], - typedRows: ['› review the change'], - promptGlyphBoldRows: [true], - rowsBelow: ['gpt-5.6 · ~/repo'], - typedRowsBelow: ['gpt-5.6 · ~/repo'], - beforeCursor: '› review the change', - afterCursor: '', - rawAfterCursor: '', - cursorHidden: false, - cursorViewportRow: 4 - }) - ).toMatchObject({ text: 'review the change', promptGlyph: '›' }) - }) - - it('recognizes a Codex composer with a context-only status footer', () => { - expect( - detectTerminalComposerDraft({ - rows: ['› review the change'], - typedRows: ['› review the change'], - promptGlyphBoldRows: [true], - rowsBelow: ['', ' Context 12% used'], - typedRowsBelow: ['', ''], - rowsBelowWrapped: [false, false], - beforeCursor: '› review the change', - afterCursor: '', - rawAfterCursor: '', - cursorHidden: false, - cursorViewportRow: 4 - }) - ).toMatchObject({ text: 'review the change', promptGlyph: '›' }) - }) - it('recognizes a Codex composer with an arbitrary dimmed thread-title footer', () => { expect( detectTerminalComposerDraft({ @@ -233,23 +123,6 @@ describe('detectTerminalComposerDraft', () => { expect(hasTerminalComposerPlaceholder(context)).toBe(false) }) - it('keeps the side-thread placeholder out of draft metadata', () => { - expect( - detectTerminalComposerDraft({ - rows: ['› Ask a follow-up question'], - typedRows: ['›'], - promptGlyphBoldRows: [true], - rowsBelow: [], - typedRowsBelow: [], - beforeCursor: '› ', - afterCursor: '', - rawAfterCursor: 'Ask a follow-up question', - cursorHidden: false, - cursorViewportRow: 4 - }) - ).toBeNull() - }) - it('keeps a wrapped stock placeholder out of draft metadata', () => { expect( detectTerminalComposerDraft({ @@ -267,25 +140,6 @@ describe('detectTerminalComposerDraft', () => { ).toBeNull() }) - it('joins soft-wrapped continuation rows without inserting a newline', () => { - expect( - detectTerminalComposerDraft({ - rows: ['────────', '❯ proceed with the'], - typedRows: ['────────', '❯'], - promptGlyphBoldRows: [false, false], - rowsWrapped: [false, false], - rowsBelow: ['release'], - typedRowsBelow: [''], - rowsBelowWrapped: [true], - beforeCursor: '❯ ', - afterCursor: '', - rawAfterCursor: 'proceed with the ', - cursorHidden: false, - cursorViewportRow: 8 - })?.text - ).toBe('proceed with the release') - }) - it('keeps a draft continuation containing a middle dot', () => { expect( detectTerminalComposerDraft({ diff --git a/src/shared/terminal-kitty-keyboard-mode-tracker.test.ts b/src/shared/terminal-kitty-keyboard-mode-tracker.test.ts index ab63ac4d61f..540fd8b74d7 100644 --- a/src/shared/terminal-kitty-keyboard-mode-tracker.test.ts +++ b/src/shared/terminal-kitty-keyboard-mode-tracker.test.ts @@ -4,13 +4,6 @@ import { TerminalKittyKeyboardModeTracker } from './terminal-kitty-keyboard-mode import { CONFIRMED_SHELL_MODE_RESET } from './terminal-mode-reset-profiles' describe('TerminalKittyKeyboardModeTracker', () => { - it('starts inactive and ignores non-kitty sequences', () => { - const tracker = new TerminalKittyKeyboardModeTracker() - expect(tracker.flags).toBe(0) - tracker.scan('plain output \x1b[?2004h\x1b[38;5;10mcolored\x1b[0m') - expect(tracker.flags).toBe(0) - }) - // Missing snapshot data must not reset already observed mode flags. it('treats a nullish chunk as a no-op', () => { const tracker = new TerminalKittyKeyboardModeTracker() @@ -23,12 +16,6 @@ describe('TerminalKittyKeyboardModeTracker', () => { expect(tracker.snapshotFlags).toBe(5) }) - it('does not treat CSI u (restore cursor) or the CSI ? u query as kitty state', () => { - const tracker = new TerminalKittyKeyboardModeTracker() - tracker.scan('\x1b[u\x1b[?u') - expect(tracker.flags).toBe(0) - }) - it('tracks push and pop like xterm, including the pop-to-empty zeroing', () => { const tracker = new TerminalKittyKeyboardModeTracker() tracker.scan('\x1b[>1u') @@ -57,17 +44,6 @@ describe('TerminalKittyKeyboardModeTracker', () => { expect(tracker.flags).toBe(4) }) - it("clears state for Orca's defensive reset sequence and RIS", () => { - const tracker = new TerminalKittyKeyboardModeTracker() - tracker.scan('\x1b[>1u') - tracker.scan('\x1b[<99u\x1b[=0u') - expect(tracker.flags).toBe(0) - - tracker.scan('\x1b[>1u') - tracker.scan('\x1bc') - expect(tracker.flags).toBe(0) - }) - // Why: a confirmed-shell reset lands between live chunks in both records, so they must agree. it('agrees with xterm when the confirmed-shell reset interrupts a split live sequence', async () => { const term = new Terminal({ allowProposedApi: true, vtExtensions: { kittyKeyboard: true } }) @@ -95,21 +71,6 @@ describe('TerminalKittyKeyboardModeTracker', () => { term.dispose() }) - it('keeps per-screen flags across alternate-screen switches', () => { - const tracker = new TerminalKittyKeyboardModeTracker() - tracker.scan('\x1b[>1u') - expect(tracker.flags).toBe(1) - tracker.scan('\x1b[?1049h') - expect(tracker.hasObservedAlternateScreenSwitch).toBe(true) - expect(tracker.isAlternateScreen).toBe(true) - expect(tracker.flags).toBe(0) - tracker.scan('\x1b[>2u') - expect(tracker.flags).toBe(2) - tracker.scan('\x1b[?1049l') - expect(tracker.isAlternateScreen).toBe(false) - expect(tracker.flags).toBe(1) - }) - it('handles sequences split across chunks and C1 CSI', () => { const tracker = new TerminalKittyKeyboardModeTracker() tracker.scan('\x1b[>') @@ -122,22 +83,6 @@ describe('TerminalKittyKeyboardModeTracker', () => { expect(tracker.flags).toBe(7) }) - it('leaves negotiated state unchanged for plain output chunks', () => { - const tracker = new TerminalKittyKeyboardModeTracker() - tracker.scan('\x1b[>1u') - tracker.scan('shell output '.repeat(100)) - expect(tracker.flags).toBe(1) - expect(tracker.snapshotFlags).toBe(1) - }) - - it('caps the mirrored stack without losing the current flags', () => { - const tracker = new TerminalKittyKeyboardModeTracker() - for (let i = 0; i < 40; i++) { - tracker.scan(`\x1b[>${(i % 3) + 1}u`) - } - expect(tracker.flags).toBe((39 % 3) + 1) - }) - it('reset() returns to the inactive state', () => { const tracker = new TerminalKittyKeyboardModeTracker() tracker.scan('\x1b[>1u\x1b[?1049h\x1b[>2u') @@ -206,19 +151,6 @@ describe('TerminalKittyKeyboardModeTracker', () => { expect(fresh.snapshotFlags).toBeUndefined() }) - it('restores known 0 and known 8 onto the active screen', () => { - const inactive = new TerminalKittyKeyboardModeTracker() - inactive.resetForSnapshot() - inactive.restoreSnapshotFlags(0) - expect(inactive.snapshotFlags).toBe(0) - - const negotiated = new TerminalKittyKeyboardModeTracker() - negotiated.resetForSnapshot() - negotiated.restoreSnapshotFlags(8) - expect(negotiated.flags).toBe(8) - expect(negotiated.snapshotFlags).toBe(8) - }) - it('ignores values that are not non-negative safe integers', () => { const tracker = new TerminalKittyKeyboardModeTracker() tracker.resetForSnapshot() @@ -322,16 +254,6 @@ describe('TerminalKittyKeyboardModeTracker', () => { tracker.restoreSnapshotFlags(8) expect(tracker.hasProvenBaseline).toBe(true) }) - - it('grounds on RIS and DECSTR arriving in the stream', () => { - const ris = new TerminalKittyKeyboardModeTracker() - ris.scan('\x1bc') - expect(ris.hasProvenBaseline).toBe(true) - - const softReset = new TerminalKittyKeyboardModeTracker() - softReset.scan('\x1b[!p') - expect(softReset.hasProvenBaseline).toBe(true) - }) }) // Why: xterm ignores CSI u while the protocol is withheld, so the mirror must stay at 0 too.