mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +00:00
test: remove low-value provider and renderer assertions (#40279)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,79 +0,0 @@
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { AgentSessionJournalIdentity } from '../../shared/agent-session/journal/agent-session-journal-identity'
|
||||
import { openAgentSessionJournal } from '../native-chat/agent-session-journal/journal-store-factory'
|
||||
import { createDeferredStructuredAgentSessionEventSink } from '../native-chat/agent-session-wire/journal-ingress/structured-agent-session-event-sink'
|
||||
import { createClaudeJournalTranslator } from './structured/journal/claude-structured-journal-translation'
|
||||
import {
|
||||
closeTestJournalHostDatabases,
|
||||
openTestJournalHostDatabase
|
||||
} from '../native-chat/agent-session-journal/store-lifecycle/journal-host-database-test-support'
|
||||
import { testEventSinkLogging } from '../native-chat/agent-session-wire/host/testing/structured-agent-session-logger-test-support'
|
||||
import { claudeProviderHandle } from '../../shared/agent-session/lifecycle/agent-session-provider-handle-encoding'
|
||||
|
||||
const IDENTITY: AgentSessionJournalIdentity = {
|
||||
sessionId: 'session-1',
|
||||
workspaceId: 'workspace-1',
|
||||
hostId: 'host-1',
|
||||
agent: 'claude',
|
||||
providerHandle: claudeProviderHandle('provider-1', 'leaf-1')
|
||||
}
|
||||
|
||||
/** Informational frame shape from CLI 2.1.280. */
|
||||
function informational(
|
||||
level: 'info' | 'notice' | 'suggestion' | 'warning',
|
||||
content: string
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
type: 'system',
|
||||
subtype: 'informational',
|
||||
content,
|
||||
level,
|
||||
uuid: `5d3c1f0e-7a2b-4c6d-9e8f-00000000000${level.length}`,
|
||||
session_id: 'provider-1'
|
||||
}
|
||||
}
|
||||
|
||||
let root = ''
|
||||
|
||||
beforeEach(async () => {
|
||||
root = await mkdtemp(join(tmpdir(), 'orca-claude-informational-'))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await closeTestJournalHostDatabases()
|
||||
await rm(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
async function itemsFor(frames: Record<string, unknown>[]) {
|
||||
const journal = await openAgentSessionJournal({
|
||||
identity: IDENTITY,
|
||||
database: openTestJournalHostDatabase(root),
|
||||
now: () => 1_700_000_000_000,
|
||||
mintEpoch: () => 'epoch-1'
|
||||
})
|
||||
const deferred = createDeferredStructuredAgentSessionEventSink(testEventSinkLogging())
|
||||
deferred.bind({ journal, fence: 1, publish: vi.fn() })
|
||||
const translator = createClaudeJournalTranslator({ sink: deferred.sink, fallbackIdPrefix: '1' })
|
||||
for (const frame of frames) {
|
||||
translator.handle({ type: 'message', sessionId: 'orca-session', message: frame })
|
||||
await deferred.drained()
|
||||
}
|
||||
return journal.snapshot().items.map((item) => item.body)
|
||||
}
|
||||
|
||||
describe('a Claude informational frame', () => {
|
||||
it.each(['info', 'notice', 'suggestion'] as const)('writes no row at level %s', async (level) => {
|
||||
expect(await itemsFor([informational(level, `A ${level} note`)])).toEqual([])
|
||||
})
|
||||
|
||||
it('writes its own words as one warning row at level warning', async () => {
|
||||
const content =
|
||||
'UserPromptSubmit operation blocked by hook: secrets are not allowed\n\nOriginal prompt: deploy it'
|
||||
const items = await itemsFor([informational('warning', content)])
|
||||
|
||||
expect(items).toEqual([{ kind: 'status', tone: 'warning', text: content }])
|
||||
})
|
||||
})
|
||||
@@ -55,7 +55,6 @@ describe('claude model catalog probe', () => {
|
||||
it.each([
|
||||
[[], [], ['/orca-probe', '/orca-probe'], 'user,project,local'],
|
||||
[['node_modules/claude/cli.js'], [], ['/chat-a', '/chat-b'], 'user'],
|
||||
[['--env-file=work env', '--', 'claude'], [], ['/chat-a', '/chat-b'], 'user'],
|
||||
[[], ['--settings', 'settings.json'], ['/chat-a', '/chat-b'], 'user']
|
||||
])(
|
||||
'lists prefix %j with arguments %j from %j, reading %s settings',
|
||||
|
||||
@@ -14,7 +14,7 @@ import { HOST_TEST_SESSION as SESSION } from '../../native-chat/agent-session-wi
|
||||
const claudeTurnEnds = (connection: Parameters<typeof claudeFrame>[0]) =>
|
||||
claudeFrame(connection, { type: 'system', subtype: 'session_state_changed', state: 'idle' })
|
||||
|
||||
it.each(['low', 'high', undefined])(
|
||||
it.each(['high', undefined])(
|
||||
'admits accepted high independently of resolved effort (effective %s)',
|
||||
async (effective) => {
|
||||
const models = [
|
||||
|
||||
@@ -46,16 +46,6 @@ const SETTINGS = {
|
||||
effective: { model: 'haiku' },
|
||||
sources: [{ source: 'userSettings', settings: { model: 'haiku' } }],
|
||||
applied: { model: 'claude-haiku-4-5-20251001', effort: null, advisor: null, ultracode: false }
|
||||
},
|
||||
envSonnet: {
|
||||
effective: {},
|
||||
sources: [],
|
||||
applied: { model: 'claude-sonnet-5', effort: 'high', advisor: null, ultracode: false }
|
||||
},
|
||||
settingsHaikuEnvSonnet: {
|
||||
effective: { model: 'haiku' },
|
||||
sources: [{ source: 'userSettings', settings: { model: 'haiku' } }],
|
||||
applied: { model: 'claude-sonnet-5', effort: 'high', advisor: null, ultracode: false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,29 +59,7 @@ function startedWithoutATurn(settings: unknown) {
|
||||
})
|
||||
}
|
||||
|
||||
async function readCurrent(settings: unknown) {
|
||||
const adapter = await acquired(startedWithoutATurn(settings))
|
||||
return (await adapter.readOptions({ sessionId: 'session-1', fence: 7 })).current
|
||||
}
|
||||
|
||||
describe('Claude model before the first turn', () => {
|
||||
it.each([
|
||||
['nothing overrides the default', SETTINGS.noOverride, 'claude-opus-5-5[1m]', 'medium'],
|
||||
['settings name a model', SETTINGS.settingsHaiku, 'claude-haiku-4-5-20251001', undefined],
|
||||
['the env names a model', SETTINGS.envSonnet, 'claude-sonnet-5', 'high'],
|
||||
['the env overrides the settings', SETTINGS.settingsHaikuEnvSonnet, 'claude-sonnet-5', 'high']
|
||||
])(
|
||||
'names what Claude will apply when %s, unconfirmed',
|
||||
async (_case, settings, model, effort) => {
|
||||
const current = await readCurrent(settings)
|
||||
|
||||
expect(current.model).toBe(model)
|
||||
expect(current.effort).toBe(effort)
|
||||
expect(current.confirmed ?? []).not.toContain('model')
|
||||
expect(current.confirmed ?? []).not.toContain('effort')
|
||||
}
|
||||
)
|
||||
|
||||
it('uses native applied effort despite settings High, before and after a reply', async () => {
|
||||
// Claude Code 2.1.318: no flag applies Medium; --effort low applies Low.
|
||||
const claude = startedWithoutATurn({
|
||||
@@ -156,13 +124,6 @@ describe('Claude model before the first turn', () => {
|
||||
await adapter.closeAll()
|
||||
})
|
||||
|
||||
it('marks an unknown applied model as defaulted for the host to resolve', async () => {
|
||||
const current = await readCurrent({ effective: {}, sources: [] })
|
||||
|
||||
expect(current.defaulted).toEqual(['model'])
|
||||
expect(current.effort).toBeUndefined()
|
||||
})
|
||||
|
||||
it('lets the first turn confirm or correct it', async () => {
|
||||
const claude = startedWithoutATurn(SETTINGS.settingsHaiku)
|
||||
const adapter = await acquired(claude)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
claudeChildEnv,
|
||||
resolveClaudeChildEnvSources,
|
||||
withOrcaClaudeEntrypoint
|
||||
} from './claude-structured-child-env'
|
||||
@@ -32,21 +31,6 @@ it('resolves a custom Claude command against the login shell PATH', async () =>
|
||||
}
|
||||
})
|
||||
|
||||
it('names Orca as the Claude client unless the launch environment sets one', () => {
|
||||
const sources = {
|
||||
command: 'claude',
|
||||
prefixArgs: [],
|
||||
overlay: undefined,
|
||||
inheritedEnv: { CLAUDE_CODE_ENTRYPOINT: 'cli' }
|
||||
}
|
||||
const options = { runtimeOnPath: false }
|
||||
expect(claudeChildEnv(sources, undefined, options).CLAUDE_CODE_ENTRYPOINT).toBe('orca')
|
||||
const overlay = { CLAUDE_CODE_ENTRYPOINT: 'custom' }
|
||||
expect(claudeChildEnv({ ...sources, overlay }, undefined, options).CLAUDE_CODE_ENTRYPOINT).toBe(
|
||||
'custom'
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps a differently cased Windows launch value over the Orca default', () => {
|
||||
const inherited = { Claude_Code_Entrypoint: 'cli', PATH: 'C:\\bin' }
|
||||
expect(
|
||||
|
||||
@@ -15,12 +15,7 @@ const MODEL_ROW = {
|
||||
}
|
||||
|
||||
describe('codex model catalog probe', () => {
|
||||
it.each([
|
||||
[[]],
|
||||
[['node_modules/@openai/codex/bin/codex.js']],
|
||||
[['--env-file=work env', '--', 'codex']],
|
||||
[['-c', 'model_instructions_file="./prompt.md"']]
|
||||
])(
|
||||
it.each([[[]]])(
|
||||
"lists a command line with arguments %j from Orca's folder for every chat",
|
||||
async (prefixArgs) => {
|
||||
// The chat's repo never picks the list, so no listing runs in a chat's folder.
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { childSpawnMock, resolveCodexCommandMock, ptySpawnMock } = vi.hoisted(() => ({
|
||||
childSpawnMock: vi.fn(),
|
||||
resolveCodexCommandMock: vi.fn(),
|
||||
ptySpawnMock: vi.fn()
|
||||
}))
|
||||
|
||||
// Keep child-process assertions in this suite; worker transport is covered separately.
|
||||
vi.mock(
|
||||
'../../../worker-thread-child-process/worker-thread-child-process',
|
||||
() =>
|
||||
import('../../../worker-thread-child-process/worker-thread-child-process-in-process.test-fixtures')
|
||||
)
|
||||
vi.mock('node:child_process', () => ({
|
||||
spawn: childSpawnMock
|
||||
}))
|
||||
|
||||
// Mock recovery so its process-table import does not require real execFile.
|
||||
vi.mock('../../../codex/history/index/codex-state-db-backfill-recovery', () => ({
|
||||
startCodexStateDbBackfillRecoveryInBackground: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../../codex-cli/command', () => ({
|
||||
resolveCodexCommand: resolveCodexCommandMock
|
||||
}))
|
||||
|
||||
vi.mock('node-pty', () => ({
|
||||
spawn: ptySpawnMock
|
||||
}))
|
||||
|
||||
vi.mock('./codex-auth-presence', () => ({
|
||||
probeCodexAuthPresence: vi.fn(() => 'present')
|
||||
}))
|
||||
|
||||
import { fetchCodexRateLimits } from './codex-fetcher'
|
||||
|
||||
function makeRpcChild() {
|
||||
const child = new EventEmitter() as EventEmitter & {
|
||||
stdout: EventEmitter
|
||||
stderr: EventEmitter
|
||||
stdin: EventEmitter & { write: ReturnType<typeof vi.fn>; end: ReturnType<typeof vi.fn> }
|
||||
kill: ReturnType<typeof vi.fn>
|
||||
exitCode: number | null
|
||||
}
|
||||
child.stdout = new EventEmitter()
|
||||
child.stderr = new EventEmitter()
|
||||
// Graceful shutdown must wait for EOF/signal to produce exit, as real app-server does.
|
||||
const exitNow = (): void => {
|
||||
child.exitCode = 0
|
||||
child.emit('exit', 0, null)
|
||||
}
|
||||
child.stdin = Object.assign(new EventEmitter(), { write: vi.fn(), end: vi.fn(exitNow) })
|
||||
child.exitCode = null
|
||||
child.kill = vi.fn(() => {
|
||||
exitNow()
|
||||
return true
|
||||
})
|
||||
return child
|
||||
}
|
||||
|
||||
describe('fetchCodexRateLimits auth errors', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.clearAllMocks()
|
||||
resolveCodexCommandMock.mockReturnValue('codex')
|
||||
vi.stubGlobal('fetch', vi.fn())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('returns Codex RPC auth refresh errors without masking them behind a fallback', async () => {
|
||||
const rpcChild = makeRpcChild()
|
||||
const authError =
|
||||
'Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.'
|
||||
|
||||
childSpawnMock.mockReturnValue(rpcChild)
|
||||
rpcChild.stdin.write.mockImplementation((line: string) => {
|
||||
const msg = JSON.parse(line) as { id?: number; method?: string }
|
||||
if (msg.method === 'initialize') {
|
||||
setTimeout(() => {
|
||||
rpcChild.stdout.emit(
|
||||
'data',
|
||||
Buffer.from(`${JSON.stringify({ jsonrpc: '2.0', id: msg.id, result: {} })}\n`)
|
||||
)
|
||||
}, 0)
|
||||
}
|
||||
if (msg.method === 'account/rateLimits/read') {
|
||||
setTimeout(() => {
|
||||
rpcChild.stdout.emit(
|
||||
'data',
|
||||
Buffer.from(
|
||||
`${JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: msg.id,
|
||||
error: { code: -32000, message: authError }
|
||||
})}\n`
|
||||
)
|
||||
)
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
|
||||
const resultPromise = fetchCodexRateLimits()
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
|
||||
await expect(resultPromise).resolves.toMatchObject({
|
||||
provider: 'codex',
|
||||
session: null,
|
||||
weekly: null,
|
||||
status: 'error',
|
||||
error: authError
|
||||
})
|
||||
expect(fetch).not.toHaveBeenCalled()
|
||||
expect(ptySpawnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns the app-server chatgpt-auth-required error without falling back', async () => {
|
||||
const rpcChild = makeRpcChild()
|
||||
const authError = 'chatgpt authentication required to read rate limits'
|
||||
|
||||
childSpawnMock.mockReturnValue(rpcChild)
|
||||
rpcChild.stdin.write.mockImplementation((line: string) => {
|
||||
const msg = JSON.parse(line) as { id?: number; method?: string }
|
||||
if (msg.method === 'initialize') {
|
||||
setTimeout(() => {
|
||||
rpcChild.stdout.emit(
|
||||
'data',
|
||||
Buffer.from(`${JSON.stringify({ jsonrpc: '2.0', id: msg.id, result: {} })}\n`)
|
||||
)
|
||||
}, 0)
|
||||
}
|
||||
if (msg.method === 'account/rateLimits/read') {
|
||||
setTimeout(() => {
|
||||
rpcChild.stdout.emit(
|
||||
'data',
|
||||
Buffer.from(
|
||||
`${JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: msg.id,
|
||||
error: { code: -32600, message: authError }
|
||||
})}\n`
|
||||
)
|
||||
)
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
|
||||
const resultPromise = fetchCodexRateLimits()
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
|
||||
await expect(resultPromise).resolves.toMatchObject({
|
||||
provider: 'codex',
|
||||
session: null,
|
||||
weekly: null,
|
||||
status: 'error',
|
||||
error: authError
|
||||
})
|
||||
expect(fetch).not.toHaveBeenCalled()
|
||||
expect(ptySpawnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,206 +0,0 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as RunProcess from '@orca/process-host'
|
||||
|
||||
const { ptySpawnMock, resolveCodexCommandMock, stubScript } = vi.hoisted(() => ({
|
||||
stubScript: { path: '' },
|
||||
ptySpawnMock: vi.fn(),
|
||||
resolveCodexCommandMock: vi.fn()
|
||||
}))
|
||||
|
||||
// Run the Node fixture through the real process chokepoint; worker transport has separate coverage.
|
||||
vi.mock(
|
||||
'../../../worker-thread-child-process/worker-thread-child-process',
|
||||
() =>
|
||||
import('../../../worker-thread-child-process/worker-thread-child-process-in-process.test-fixtures')
|
||||
)
|
||||
vi.mock('@orca/process-host', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof RunProcess>()
|
||||
return {
|
||||
...actual,
|
||||
spawnProcess: (spec: Parameters<typeof actual.spawnProcess>[0]) =>
|
||||
actual.spawnProcess({
|
||||
...spec,
|
||||
program: process.execPath,
|
||||
args: [stubScript.path, ...(spec.args ?? [])]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../codex-cli/command', () => ({
|
||||
resolveCodexCommand: resolveCodexCommandMock
|
||||
}))
|
||||
|
||||
vi.mock('node-pty', () => ({
|
||||
spawn: ptySpawnMock
|
||||
}))
|
||||
|
||||
vi.mock('./codex-auth-presence', () => ({
|
||||
probeCodexAuthPresence: vi.fn(() => 'present')
|
||||
}))
|
||||
|
||||
vi.mock('../../../codex/history/index/codex-state-db', () => ({
|
||||
isCodexStateDbBackfillPending: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
vi.mock('../../../codex/history/index/codex-state-db-backfill-recovery', () => ({
|
||||
startCodexStateDbBackfillRecoveryInBackground: vi.fn(() => Promise.resolve(null))
|
||||
}))
|
||||
|
||||
import { fetchCodexRateLimits } from './codex-fetcher'
|
||||
|
||||
const STUB_CODEX_SOURCE = `
|
||||
const expectedArgs = [
|
||||
'-c',
|
||||
'approval_policy=never',
|
||||
'-c',
|
||||
'features.plugins=false',
|
||||
'-s',
|
||||
'read-only',
|
||||
'-a',
|
||||
'never',
|
||||
'app-server'
|
||||
]
|
||||
if (JSON.stringify(process.argv.slice(2)) !== JSON.stringify(expectedArgs)) {
|
||||
process.stderr.write(
|
||||
"error: invalid value 'untrusted' for '--ask-for-approval <APPROVAL_POLICY>'\\n" +
|
||||
' [possible values: on-request, never]\\n'
|
||||
)
|
||||
process.exit(2)
|
||||
}
|
||||
if (process.env.CODEX_HOME !== process.env.ORCA_EXPECTED_CODEX_HOME) {
|
||||
process.stderr.write('managed CODEX_HOME was not preserved\\n')
|
||||
process.exit(3)
|
||||
}
|
||||
if (process.env.ORCA_EXPECTED_PROXY_ENV) {
|
||||
for (const [key, value] of Object.entries(JSON.parse(process.env.ORCA_EXPECTED_PROXY_ENV))) {
|
||||
if (process.env[key] !== value) {
|
||||
process.stderr.write('probe proxy environment mismatch: ' + key + '\\n')
|
||||
process.exit(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
let buffer = ''
|
||||
function send(message) {
|
||||
process.stdout.write(JSON.stringify(message) + '\\n')
|
||||
}
|
||||
process.stdin.setEncoding('utf8')
|
||||
process.stdin.on('data', (chunk) => {
|
||||
buffer += chunk
|
||||
let newlineIndex
|
||||
while ((newlineIndex = buffer.indexOf('\\n')) !== -1) {
|
||||
const line = buffer.slice(0, newlineIndex).trim()
|
||||
buffer = buffer.slice(newlineIndex + 1)
|
||||
if (!line) continue
|
||||
const message = JSON.parse(line)
|
||||
if (message.method === 'initialize') {
|
||||
send({ id: message.id, result: { userAgent: 'codex-contract/0.149.0' } })
|
||||
continue
|
||||
}
|
||||
if (message.method === 'account/rateLimits/read') {
|
||||
send({
|
||||
id: message.id,
|
||||
result: {
|
||||
rateLimits: {
|
||||
primary: { usedPercent: 17, windowDurationMins: 300 },
|
||||
secondary: { usedPercent: 29, windowDurationMins: 10080 }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
process.stdin.on('end', () => process.exit(0))
|
||||
`
|
||||
|
||||
let tempRoot: string
|
||||
let stubPath: string
|
||||
let previousExpectedHome: string | undefined
|
||||
|
||||
describe('Codex rate-limit process contract', () => {
|
||||
beforeEach(() => {
|
||||
tempRoot = mkdtempSync(join(tmpdir(), 'orca-codex-rate-limit-contract-'))
|
||||
stubPath = join(tempRoot, 'codex-contract.cjs')
|
||||
writeFileSync(stubPath, STUB_CODEX_SOURCE)
|
||||
previousExpectedHome = process.env.ORCA_EXPECTED_CODEX_HOME
|
||||
process.env.ORCA_EXPECTED_CODEX_HOME = join(tempRoot, 'managed-codex-home')
|
||||
resolveCodexCommandMock.mockReturnValue('codex')
|
||||
stubScript.path = stubPath
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
if (previousExpectedHome === undefined) {
|
||||
delete process.env.ORCA_EXPECTED_CODEX_HOME
|
||||
} else {
|
||||
process.env.ORCA_EXPECTED_CODEX_HOME = previousExpectedHome
|
||||
}
|
||||
rmSync(tempRoot, { recursive: true, force: true })
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('starts a read-only non-interactive app-server with the managed home', async () => {
|
||||
await expect(
|
||||
fetchCodexRateLimits({
|
||||
codexHomePath: process.env.ORCA_EXPECTED_CODEX_HOME
|
||||
})
|
||||
).resolves.toMatchObject({
|
||||
provider: 'codex',
|
||||
session: { usedPercent: 17, windowMinutes: 300 },
|
||||
weekly: { usedPercent: 29, windowMinutes: 10080 },
|
||||
status: 'ok',
|
||||
error: null
|
||||
})
|
||||
expect(ptySpawnMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(['localhost;*.fixture.invalid', ''])(
|
||||
'passes configured proxy and bypass %j to the actual probe',
|
||||
async (bypassRules) => {
|
||||
const proxy = 'http://proxy.fixture.invalid:8080'
|
||||
const expected = {
|
||||
HTTP_PROXY: proxy,
|
||||
HTTPS_PROXY: proxy,
|
||||
ALL_PROXY: proxy,
|
||||
http_proxy: proxy,
|
||||
https_proxy: proxy,
|
||||
all_proxy: proxy,
|
||||
NO_PROXY: bypassRules.replaceAll(';', ','),
|
||||
no_proxy: bypassRules.replaceAll(';', ',')
|
||||
}
|
||||
vi.stubEnv('NO_PROXY', 'inherited.invalid')
|
||||
vi.stubEnv('no_proxy', 'inherited.invalid')
|
||||
vi.stubEnv('ORCA_EXPECTED_PROXY_ENV', JSON.stringify(expected))
|
||||
|
||||
await expect(
|
||||
fetchCodexRateLimits({
|
||||
codexHomePath: process.env.ORCA_EXPECTED_CODEX_HOME,
|
||||
networkProxySettings: { httpProxyUrl: proxy, httpProxyBypassRules: bypassRules }
|
||||
})
|
||||
).resolves.toMatchObject({ status: 'ok', session: { usedPercent: 17 } })
|
||||
expect(process.env.NO_PROXY).toBe('inherited.invalid')
|
||||
}
|
||||
)
|
||||
|
||||
it.each([undefined, { httpProxyUrl: '' }, { httpProxyUrl: 'file:///invalid-proxy' }])(
|
||||
'preserves inherited proxy settings when app settings are unset or invalid: %j',
|
||||
async (networkProxySettings) => {
|
||||
const inherited = {
|
||||
HTTPS_PROXY: 'http://inherited.fixture.invalid:8080',
|
||||
NO_PROXY: 'fixture.invalid'
|
||||
}
|
||||
for (const [key, value] of Object.entries(inherited)) {
|
||||
vi.stubEnv(key, value)
|
||||
}
|
||||
vi.stubEnv('ORCA_EXPECTED_PROXY_ENV', JSON.stringify(inherited))
|
||||
await expect(
|
||||
fetchCodexRateLimits({
|
||||
codexHomePath: process.env.ORCA_EXPECTED_CODEX_HOME,
|
||||
networkProxySettings
|
||||
})
|
||||
).resolves.toMatchObject({ status: 'ok', session: { usedPercent: 17 } })
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -86,29 +86,6 @@ describe('Codex RPC exit diagnostics', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('names the exit code and what the child reported', async () => {
|
||||
const result = await runStub(
|
||||
"error: invalid value 'untrusted' for '--ask-for-approval <APPROVAL_POLICY>'\n" +
|
||||
" [possible values: on-request, never]\n\nFor more information, try '--help'.\n",
|
||||
2
|
||||
)
|
||||
|
||||
expect(result.error).toContain('Codex RPC process exited (exit code 2)')
|
||||
expect(result.error).toContain("invalid value 'untrusted'")
|
||||
expect(result.error).toContain("try '--help'")
|
||||
})
|
||||
|
||||
it('classifies a dead refresh token so the re-auth warning fires', async () => {
|
||||
const result = await runStub(
|
||||
'2026-08-20T18:04:11.221Z INFO codex_core::auth: refreshing ChatGPT tokens\n' +
|
||||
'ERROR: The ChatGPT access token could not be refreshed; please sign in again.\n',
|
||||
1
|
||||
)
|
||||
|
||||
expect(result.error).toBe('Your ChatGPT session could not be refreshed. Please sign in again.')
|
||||
expect(isCodexAuthError(result.error)).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps the re-auth phrase that path redaction would swallow', async () => {
|
||||
const result = await runStub(
|
||||
`ERROR: /Users/haris/.codex/auth.json is stale — please sign in again.\n`,
|
||||
|
||||
@@ -6,16 +6,6 @@ function record(...fields: string[]): string {
|
||||
}
|
||||
|
||||
describe('WSL Claude plugin metadata', () => {
|
||||
it('decodes present files and retains missing files as null', () => {
|
||||
const settings = JSON.stringify({ enabledPlugins: { 'plugin@market': true } })
|
||||
const output = [
|
||||
record('F', '0', '1', Buffer.from(settings).toString('base64')),
|
||||
record('F', '1', '0', '')
|
||||
].join('')
|
||||
|
||||
expect(parseWslClaudePluginMetadataOutput(output, 2)).toEqual([settings, null])
|
||||
})
|
||||
|
||||
it('rejects malformed metadata responses', () => {
|
||||
expect(() => parseWslClaudePluginMetadataOutput(record('F', '4', '1', ''), 1)).toThrow(
|
||||
'invalid response'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { chmod, lstat, mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
|
||||
import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { gunzipSync, gzipSync } from 'node:zlib'
|
||||
@@ -136,33 +136,6 @@ describe('skill package creation and extraction', () => {
|
||||
).rejects.toThrow('skill-package-tar-truncated')
|
||||
})
|
||||
|
||||
it('cancels during streamed extraction and removes partial bytes', async () => {
|
||||
const root = await temporaryDirectory()
|
||||
const created = await createSkillPackageArchive({
|
||||
sourceDirectory: await createSkill(root),
|
||||
archivePath: join(root, 'package.tar.gz'),
|
||||
packageId: 'package_1',
|
||||
versionId: 'version_1'
|
||||
})
|
||||
let checks = 0
|
||||
const signal = {
|
||||
get aborted() {
|
||||
checks += 1
|
||||
return checks >= 3
|
||||
}
|
||||
} as AbortSignal
|
||||
const destinationDirectory = join(root, 'cancelled-extraction')
|
||||
|
||||
await expect(
|
||||
extractSkillPackageArchive({
|
||||
archivePath: created.archivePath,
|
||||
destinationDirectory,
|
||||
signal
|
||||
})
|
||||
).rejects.toThrow('skill-install-cancelled')
|
||||
await expect(lstat(destinationDirectory)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
})
|
||||
|
||||
it('rejects content checksum and SKILL identity mismatches', async () => {
|
||||
const root = await temporaryDirectory()
|
||||
const created = await createSkillPackageArchive({
|
||||
|
||||
@@ -69,23 +69,4 @@ describe('SkillSharePreparationService', () => {
|
||||
|
||||
expect(prepared.skills?.[0]?.executablePaths).toEqual(['run.sh'])
|
||||
})
|
||||
|
||||
it('enforces the preparation cap while archives are still being created', async () => {
|
||||
const { root, source } = await createSource()
|
||||
const service = new SkillSharePreparationService(join(root, 'preparations'), {
|
||||
findOwnedPackageId: nameIsFree,
|
||||
publishVersion: vi.fn(),
|
||||
createShare: vi.fn()
|
||||
})
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
Array.from({ length: 9 }, () => service.prepare({ sourceDirectory: source }))
|
||||
)
|
||||
|
||||
expect(results.filter((result) => result.status === 'fulfilled')).toHaveLength(8)
|
||||
expect(results.filter((result) => result.status === 'rejected')).toHaveLength(1)
|
||||
expect(results.find((result) => result.status === 'rejected')).toMatchObject({
|
||||
reason: new Error('skill-share-preparation-limit')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -102,28 +102,6 @@ it('keeps the branch checkout strip open after selecting a badge context-menu ac
|
||||
expect(container.querySelector('form')?.textContent).toContain('Check out branch topic?')
|
||||
})
|
||||
|
||||
it('renders converging branch lanes and a detached root without duplicate child keys', async () => {
|
||||
const errors = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
getHistory.mockReset().mockResolvedValue({
|
||||
...result,
|
||||
items: [
|
||||
{ id: 'a', subject: 'a', message: 'a', parentIds: ['root'] },
|
||||
{ id: 'b', subject: 'b', message: 'b', parentIds: ['root'] },
|
||||
{ id: 'c', subject: 'c', message: 'c', parentIds: ['root'] },
|
||||
{ id: 'detached', subject: 'detached', message: 'detached', parentIds: [] },
|
||||
{ id: 'root', subject: 'root', message: 'root', parentIds: [] }
|
||||
]
|
||||
})
|
||||
await act(async () =>
|
||||
root.render(
|
||||
<TooltipProvider>
|
||||
<GitGraphTab worktreeId={workspaceId} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
)
|
||||
expect(errors.mock.calls.filter(([message]) => String(message).includes('same key'))).toEqual([])
|
||||
})
|
||||
|
||||
it('recovers silently after each later token expiry and ignores refreshes for other worktrees', async () => {
|
||||
let history: ReturnType<typeof useGitGraphHistory>
|
||||
function History() {
|
||||
|
||||
-66
@@ -18,9 +18,6 @@ import {
|
||||
useNativeChatDisclosures
|
||||
} from '../../messages/disclosure/native-chat-disclosure-store'
|
||||
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { TranslationScopeProvider } from '@/i18n/translation-scope-provider'
|
||||
|
||||
{
|
||||
const QUESTION = 'What would you like me to do next in this repo?'
|
||||
const ASK_INPUT = { questions: [{ question: QUESTION }] }
|
||||
@@ -288,66 +285,3 @@ import { TranslationScopeProvider } from '@/i18n/translation-scope-provider'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
function runHeader(container: HTMLElement): HTMLElement {
|
||||
const header = container.querySelector('button')
|
||||
if (!header) {
|
||||
throw new Error('run header did not render')
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
describe('tool run sentences', () => {
|
||||
afterEach(async () => {
|
||||
cleanup()
|
||||
await i18n.changeLanguage('en')
|
||||
})
|
||||
|
||||
it('relabels an already-mounted run and its rows when the UI language changes', async () => {
|
||||
const blocks: NativeChatBlock[] = [
|
||||
{ type: 'tool-call', name: 'Bash', input: { command: 'false' }, state: 'failed' },
|
||||
{ type: 'tool-result', output: 'exit 1', isError: true },
|
||||
{
|
||||
type: 'tool-call',
|
||||
name: 'Agent',
|
||||
input: { description: 'explore settings' },
|
||||
state: 'completed'
|
||||
}
|
||||
]
|
||||
const { container } = render(
|
||||
<NativeChatToolRun
|
||||
blocks={blocks}
|
||||
backgroundTasks={[
|
||||
{
|
||||
type: 'background-task',
|
||||
taskId: 'workflow-1',
|
||||
kind: 'workflow',
|
||||
label: 'task',
|
||||
state: 'blocked',
|
||||
outputFile: '/tmp/result.txt'
|
||||
}
|
||||
]}
|
||||
expandSignal
|
||||
/>,
|
||||
{ wrapper: TranslationScopeProvider }
|
||||
)
|
||||
const header = runHeader(container)
|
||||
expect(header).toHaveTextContent('Ran 1 command and ran 1 agent · 1 failed')
|
||||
expect(screen.getByText('Subagent')).toBeInTheDocument()
|
||||
expect(screen.getByText('Background workflow')).toBeInTheDocument()
|
||||
expect(screen.getByText('Output: /tmp/result.txt')).toBeInTheDocument()
|
||||
|
||||
await act(async () => {
|
||||
await i18n.changeLanguage('es')
|
||||
})
|
||||
|
||||
expect(header).toHaveTextContent('Se ejecutó 1 comando y se ejecutó 1 agente · 1 fallidas')
|
||||
expect(header).toHaveAccessibleName(/Llamadas a herramientas fallidas: 1/)
|
||||
expect(screen.getByText('Subagente')).toBeInTheDocument()
|
||||
expect(screen.getByText('Flujo de trabajo en segundo plano')).toBeInTheDocument()
|
||||
expect(screen.getByText('Salida: /tmp/result.txt')).toBeInTheDocument()
|
||||
expect(screen.getByText('bloqueado · con error')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
import { afterAll, describe, expect, it } from 'vitest'
|
||||
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { getDeveloperPermissionsPaneSearchEntries } from './developer-permissions-search'
|
||||
import { matchesSettingsSearch } from '../search/settings-search'
|
||||
|
||||
// Use macOS 26’s actual Local Network translations (SecurityPrivacyExtension LOCAL_NETWORK).
|
||||
const MACOS_LOCAL_NETWORK_LABEL = {
|
||||
en: 'local network',
|
||||
es: 'red local',
|
||||
ja: 'ローカルネットワーク',
|
||||
ko: '로컬 네트워크',
|
||||
zh: '本地网络'
|
||||
} as const
|
||||
|
||||
function getLanEntry() {
|
||||
const entry = getDeveloperPermissionsPaneSearchEntries().find((candidate) =>
|
||||
candidate.keywords?.includes('usb')
|
||||
)
|
||||
if (!entry) {
|
||||
throw new Error('LAN, USB, and Bluetooth search entry is missing')
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
async function searchInLocale(locale: string, query: string): Promise<boolean> {
|
||||
await i18n.changeLanguage(locale)
|
||||
return matchesSettingsSearch(query, getLanEntry())
|
||||
}
|
||||
|
||||
describe('developer permissions LAN search', () => {
|
||||
afterAll(async () => {
|
||||
await i18n.changeLanguage('en')
|
||||
})
|
||||
|
||||
it.each(Object.entries(MACOS_LOCAL_NETWORK_LABEL))(
|
||||
'finds the LAN row from the macOS Local Network wording in %s',
|
||||
async (locale, label) => {
|
||||
expect(await searchInLocale(locale, label)).toBe(true)
|
||||
}
|
||||
)
|
||||
|
||||
it('keeps the English LAN aliases findable in every locale', async () => {
|
||||
for (const locale of Object.keys(MACOS_LOCAL_NETWORK_LABEL)) {
|
||||
for (const query of ['lan', 'local network', 'local-network']) {
|
||||
expect(await searchInLocale(locale, query)).toBe(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the localized LAN wording findable', async () => {
|
||||
expect(await searchInLocale('zh', '局域网')).toBe(true)
|
||||
expect(await searchInLocale('ja', 'LAN')).toBe(true)
|
||||
expect(await searchInLocale('ko', 'LAN')).toBe(true)
|
||||
expect(await searchInLocale('es', 'LAN')).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
agentTabActionId,
|
||||
getKeybindingDefinition
|
||||
} from '../../../../../shared/keybindings/definitions'
|
||||
import type {
|
||||
KeybindingActionId,
|
||||
KeybindingDefinition
|
||||
} from '../../../../../shared/keybindings/types'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { localizeKeybindingDefinition } from './keybinding-title-copy'
|
||||
|
||||
function definition(actionId: KeybindingActionId): KeybindingDefinition {
|
||||
const found = getKeybindingDefinition(actionId)
|
||||
if (!found) {
|
||||
throw new Error(`missing ${actionId}`)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
describe('localizeKeybindingDefinition', () => {
|
||||
afterEach(async () => {
|
||||
await i18n.changeLanguage('en')
|
||||
})
|
||||
|
||||
it('localizes built-in titles, keeps English searchable, and leaves plugin titles alone', async () => {
|
||||
await i18n.changeLanguage('ko')
|
||||
const quickOpen = definition('worktree.quickOpen')
|
||||
|
||||
const localized = localizeKeybindingDefinition(quickOpen)
|
||||
expect(localized.title).not.toBe(quickOpen.title)
|
||||
expect(localized.group).not.toBe(quickOpen.group)
|
||||
expect(localized.searchKeywords).toEqual(
|
||||
expect.arrayContaining([quickOpen.title, quickOpen.group])
|
||||
)
|
||||
const agentTab = definition(agentTabActionId('claude'))
|
||||
const agentTitle = localizeKeybindingDefinition(agentTab).title
|
||||
expect(agentTitle).not.toBe(agentTab.title)
|
||||
expect(agentTitle).toContain('Claude')
|
||||
|
||||
const plugin: KeybindingDefinition = {
|
||||
...quickOpen,
|
||||
id: 'plugin:orca-samples.tasks/open',
|
||||
title: 'Open Tasks — Tasks',
|
||||
group: 'Plugins'
|
||||
}
|
||||
expect(localizeKeybindingDefinition(plugin)).toBe(plugin)
|
||||
})
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createGlobalSettingsFixture } from '../../../../../shared/global-settings-test-fixture'
|
||||
import { useAppStore } from '../../../store'
|
||||
import { TerminalInteractionSection } from './TerminalInteractionSection'
|
||||
|
||||
const i18nMock = vi.hoisted(() => {
|
||||
const translations: Record<string, string> = {}
|
||||
return { language: 'en', translations }
|
||||
})
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (key: string, defaultValue: string) => i18nMock.translations[key] ?? defaultValue,
|
||||
i18n: i18nMock
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n/localized-catalog', () => ({
|
||||
createLocalizedCatalog: (factory: () => unknown) => factory
|
||||
}))
|
||||
|
||||
describe('TerminalInteractionSection scroll on input', () => {
|
||||
beforeEach(() => {
|
||||
useAppStore.setState({ settingsSearchQuery: '' })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
i18nMock.language = 'en'
|
||||
i18nMock.translations = {}
|
||||
})
|
||||
|
||||
it('keeps the row visible for a localized search keyword', () => {
|
||||
i18nMock.language = 'de'
|
||||
i18nMock.translations = { 'components.settings.terminal.search.snap': 'einrasten' }
|
||||
useAppStore.setState({ settingsSearchQuery: 'einrasten' })
|
||||
|
||||
render(
|
||||
<TerminalInteractionSection
|
||||
settings={createGlobalSettingsFixture()}
|
||||
updateSettings={vi.fn()}
|
||||
searchQuery="einrasten"
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByRole('switch', { name: 'Scroll to Bottom on Input' })).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { BrowserPage, BrowserWorkspace } from '../../../../../shared/browser-workspace-types'
|
||||
import type { FolderWorkspace } from '../../../../../shared/folder-workspaces/folder-workspace-types'
|
||||
import type { Tab, TabGroup } from '../../../../../shared/tab-types'
|
||||
import type { Worktree } from '../../../../../shared/worktree/types'
|
||||
import '../../../../../shared/constants'
|
||||
import { ORCA_BROWSER_BLANK_URL } from '../../../../../shared/browser/browser-blank-page'
|
||||
import { folderWorkspaceKey } from '../../../../../shared/workspace-scope'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import type { AppState } from '@/store/types'
|
||||
|
||||
@@ -44,25 +42,6 @@ function makeWorktree(overrides: Partial<Worktree> = {}): Worktree {
|
||||
}
|
||||
}
|
||||
|
||||
function makeFolderWorkspace(overrides: Partial<FolderWorkspace> = {}): FolderWorkspace {
|
||||
return {
|
||||
id: 'folder-1',
|
||||
projectGroupId: 'project-1',
|
||||
name: 'Remote folder',
|
||||
folderPath: '/workspace/folder-1',
|
||||
linkedTask: null,
|
||||
comment: '',
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 0,
|
||||
lastActivityAt: 0,
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function makeWorkspace(overrides: Partial<BrowserWorkspace> = {}): BrowserWorkspace {
|
||||
return {
|
||||
id: 'ws-1',
|
||||
@@ -150,26 +129,6 @@ describe('activateBrowserPagePaletteResult', () => {
|
||||
seedStore()
|
||||
})
|
||||
|
||||
it('activates an SSH worktree through its paired-runtime owner alias', () => {
|
||||
seedStore({
|
||||
worktreesByRepo: {
|
||||
'repo-1': [
|
||||
makeWorktree({
|
||||
hostId: 'ssh:private-target',
|
||||
runtimeOwnerEnvironmentId: 'paired-host'
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
activateBrowserPagePaletteResult({ ...target, executionHostId: 'runtime:paired-host' }).status
|
||||
).toBe('activated')
|
||||
expect(mocks.activateAndRevealWorkspace).toHaveBeenCalledWith('wt-1', {
|
||||
executionHostId: 'runtime:paired-host'
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects colliding child ids before mutating either host', () => {
|
||||
seedStore({
|
||||
worktreesByRepo: {
|
||||
@@ -196,41 +155,6 @@ describe('activateBrowserPagePaletteResult', () => {
|
||||
expect(mocks.activateAndRevealWorkspace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps browser workspaces with distinct unified tabs in multiple groups activatable', () => {
|
||||
seedStore({
|
||||
unifiedTabsByWorktree: {
|
||||
'wt-1': [makeBrowserTab(), makeBrowserTab({ id: 'second-view', groupId: 'group-2' })]
|
||||
},
|
||||
groupsByWorktree: {
|
||||
'wt-1': [
|
||||
makeGroup(),
|
||||
makeGroup({ id: 'group-2', activeTabId: 'second-view', tabOrder: ['second-view'] })
|
||||
]
|
||||
}
|
||||
})
|
||||
expect(activateBrowserPagePaletteResult(target).status).toBe('activated')
|
||||
})
|
||||
|
||||
it('activates pages in remote folder workspaces', () => {
|
||||
const worktreeId = folderWorkspaceKey('folder-1')
|
||||
seedStore({
|
||||
worktreesByRepo: {},
|
||||
folderWorkspaces: [makeFolderWorkspace({ executionHostId: 'ssh:host-1' })],
|
||||
browserTabsByWorktree: { [worktreeId]: [makeWorkspace({ worktreeId })] },
|
||||
browserPagesByWorkspace: { 'ws-1': [makePage({ worktreeId })] },
|
||||
unifiedTabsByWorktree: { [worktreeId]: [makeBrowserTab({ worktreeId })] },
|
||||
groupsByWorktree: { [worktreeId]: [makeGroup({ worktreeId })] },
|
||||
activeGroupIdByWorktree: { [worktreeId]: 'group-1' }
|
||||
})
|
||||
|
||||
expect(
|
||||
activateBrowserPagePaletteResult({ pageId: 'page-1', workspaceId: 'ws-1', worktreeId })
|
||||
).toMatchObject({ status: 'activated' })
|
||||
expect(mocks.activateAndRevealWorkspace).toHaveBeenCalledWith(worktreeId, {
|
||||
executionHostId: 'ssh:host-1'
|
||||
})
|
||||
})
|
||||
|
||||
it('derives the focus target from the page url captured before worktree activation', () => {
|
||||
seedStore({
|
||||
browserPagesByWorkspace: {
|
||||
|
||||
@@ -608,32 +608,3 @@ it('replaces the full unaccepted extent, accepts once, and preserves promoted ch
|
||||
expect(reopened.delegated).toEqual(expect.arrayContaining(['model', 'effort']))
|
||||
expect(agentPickerEffectiveChoices(entry, reopened).model).toBeUndefined()
|
||||
})
|
||||
|
||||
it('orders raw and control picks once and gives one-shot and sparse consumers the same choices', () => {
|
||||
const selection = canonicalSourceControlActionSelection({ agentId: 'claude', agentArgs: '' })
|
||||
for (const edits of [
|
||||
[
|
||||
{ kind: 'arguments', text: '--model sonnet --effort high --debug' },
|
||||
{ kind: 'choice', patch: { model: 'opus' } }
|
||||
],
|
||||
[
|
||||
{ kind: 'choice', patch: { model: 'opus' } },
|
||||
{ kind: 'arguments', text: '--model sonnet --effort high --debug' }
|
||||
]
|
||||
] satisfies AgentPickerEdit[][]) {
|
||||
const draft = acceptAgentPickerArguments(
|
||||
entry,
|
||||
edits.reduce(
|
||||
(value, edit) => applyAgentPickerEdit(entry, value, edit),
|
||||
sourceControlPickerDraft(selection, entry, 'terminal')
|
||||
)
|
||||
)
|
||||
const saved = project(applySourceControlPickerDraft(selection, entry, draft))
|
||||
const chat = agentPickerLaunchInputs({ ...draft, surface: 'chat' }, entry)
|
||||
expect(saved.sessionOptions?.model).toBe(chat.sessionOptions?.model)
|
||||
expect(saved.sessionOptions?.effort).toBe(chat.sessionOptions?.effort)
|
||||
expect(saved.agentArgs).toBe('--debug')
|
||||
expect(agentPickerLaunchInputs(draft, entry).agentArgs).toContain('--debug')
|
||||
expect(draft.pendingArguments).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user