feat(settings): add Codex link controls and authenticated phone bridge
This commit is contained in:
1 parent
1acb5dd90f
commit
55a1beaf9d
22 files changed
+2857
-3
No files matched your search
@@ -0,0 +1,196 @@
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { I18nProvider } from '@/i18n/context'
|
||||
|
||||
import type {
|
||||
CodexLinkContextView,
|
||||
CodexLinkControlRequest,
|
||||
CodexLinkControlResponse,
|
||||
CodexLinkSettings as SavedSettings
|
||||
} from '../../../codex-link-control.types'
|
||||
|
||||
import { CodexLinkSettings } from './codex-link-settings'
|
||||
|
||||
let settings: SavedSettings
|
||||
let views: CodexLinkContextView[]
|
||||
let writes: CodexLinkControlRequest[]
|
||||
let failWrite: boolean
|
||||
|
||||
const control = vi.fn(async (request: CodexLinkControlRequest): Promise<CodexLinkControlResponse> => {
|
||||
const base = {
|
||||
contract_version: 'codex-link/1' as const,
|
||||
request_id: request.request_id,
|
||||
operation_id: request.operation_id
|
||||
}
|
||||
|
||||
if (request.kind === 'status_get') {
|
||||
return {
|
||||
...base,
|
||||
kind: 'status_result',
|
||||
status: {
|
||||
settings_revision: settings.settings_revision,
|
||||
runtime_mode: 'managed',
|
||||
native_available: true,
|
||||
external_admission: 'open',
|
||||
needs_restart: false,
|
||||
catalog_state: 'fresh',
|
||||
active_turns: 0,
|
||||
uncertain_operations: 0,
|
||||
last_operation_id: null,
|
||||
error: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.kind === 'context_views_get') {
|
||||
return { ...base, kind: 'context_views_result', settings_revision: settings.settings_revision, views }
|
||||
}
|
||||
|
||||
if (request.kind === 'settings_update') {
|
||||
writes.push(request)
|
||||
|
||||
if (failWrite) {
|
||||
throw new Error('outcome_unknown')
|
||||
}
|
||||
|
||||
settings = { ...settings, ...request.patch, settings_revision: settings.settings_revision + 1 }
|
||||
} else if (request.kind !== 'settings_get') {
|
||||
writes.push(request)
|
||||
|
||||
return {
|
||||
...base,
|
||||
kind: 'control_accepted',
|
||||
settings_revision: settings.settings_revision,
|
||||
operation_state: 'accepted',
|
||||
error: null
|
||||
}
|
||||
}
|
||||
|
||||
return { ...base, kind: 'settings_result', settings, available_sources: [] }
|
||||
})
|
||||
|
||||
const mount = () =>
|
||||
render(
|
||||
<I18nProvider configClient={null} initialLocale="tr">
|
||||
<CodexLinkSettings />
|
||||
</I18nProvider>
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
sessionStorage.clear()
|
||||
control.mockClear()
|
||||
writes = []
|
||||
views = []
|
||||
failWrite = false
|
||||
settings = {
|
||||
settings_revision: 7,
|
||||
enabled: false,
|
||||
compression_enabled: true,
|
||||
threshold_tokens: 250000,
|
||||
source_binding_handle: null,
|
||||
compact_route_id: 'Compact',
|
||||
legacy_intent_imported: true,
|
||||
ownership_manifest_revision: 2,
|
||||
activation_state: 'disabled'
|
||||
}
|
||||
vi.stubGlobal('hermesDesktop', { codexLink: { control } } satisfies Pick<NonNullable<typeof window.hermesDesktop>, 'codexLink'>)
|
||||
})
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('Codex connection settings', () => {
|
||||
it('shows the persisted disabled preference, without turning it on during mount', async () => {
|
||||
mount()
|
||||
const toggle = await screen.findByRole('switch', { name: 'Modelleri Codex’e bağla' })
|
||||
expect(toggle.getAttribute('aria-checked')).toBe('false')
|
||||
expect(writes).toEqual([])
|
||||
expect((screen.getByLabelText('Sıkıştırma eşiği (token)') as HTMLInputElement).value).toBe('250000')
|
||||
})
|
||||
it('uses a checkpoint-safe settings patch and rejects decimal/unsafe thresholds', async () => {
|
||||
mount()
|
||||
const input = await screen.findByLabelText('Sıkıştırma eşiği (token)')
|
||||
fireEvent.change(input, { target: { value: '1.5' } })
|
||||
expect((screen.getByRole('button', { name: 'Kaydet' }) as HTMLButtonElement).disabled).toBe(true)
|
||||
fireEvent.change(input, { target: { value: '9007199254740992' } })
|
||||
expect((screen.getByRole('button', { name: 'Kaydet' }) as HTMLButtonElement).disabled).toBe(true)
|
||||
fireEvent.change(input, { target: { value: '125000' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Kaydet' }))
|
||||
await waitFor(() => expect(writes).toHaveLength(1))
|
||||
expect(writes[0]).toMatchObject({
|
||||
kind: 'settings_update',
|
||||
expected_settings_revision: 7,
|
||||
patch: { threshold_tokens: 125000 }
|
||||
})
|
||||
})
|
||||
it('keeps an uncertain operation across remount and reconciles the exact ID/body', async () => {
|
||||
failWrite = true
|
||||
const first = mount()
|
||||
fireEvent.click(await screen.findByRole('switch', { name: 'Modelleri Codex’e bağla' }))
|
||||
await screen.findByRole('button', { name: 'İşlemi kontrol et' })
|
||||
expect(writes).toHaveLength(1)
|
||||
const original = writes[0]
|
||||
first.unmount()
|
||||
mount()
|
||||
failWrite = false
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'İşlemi kontrol et' }))
|
||||
await waitFor(() => expect(writes).toHaveLength(2))
|
||||
expect(writes[1]).toEqual(original)
|
||||
await waitFor(() => expect(sessionStorage.getItem('aiturk.codex-link.pending-control.v1')).toBeNull())
|
||||
})
|
||||
it('does not present unknown token measurement as zero or enable unsafe compaction', async () => {
|
||||
views = [
|
||||
{
|
||||
view_handle: 'view-a',
|
||||
can_compact: false,
|
||||
label: 'Ayrı sohbet',
|
||||
checkpoint_revision: 3,
|
||||
current_model_label: 'Model A',
|
||||
limiting_target_id: null,
|
||||
target_budgets: null,
|
||||
active_summary_count: 2,
|
||||
summary_tokens: null,
|
||||
summary_token_measurement: null,
|
||||
last_operation_id: null,
|
||||
last_operation_state: null,
|
||||
last_operation_error: null,
|
||||
compact_attempts: []
|
||||
}
|
||||
]
|
||||
mount()
|
||||
await screen.findByText('Ayrı sohbet')
|
||||
expect(screen.getAllByText(/Henüz ölçülmedi/).length).toBeGreaterThanOrEqual(3)
|
||||
expect((screen.getByRole('button', { name: 'Şimdi sıkıştır' }) as HTMLButtonElement).disabled).toBe(true)
|
||||
})
|
||||
it('binds manual compaction to a manager view and its exact checkpoint revision', async () => {
|
||||
views = [
|
||||
{
|
||||
view_handle: 'opaque-view-a',
|
||||
can_compact: true,
|
||||
label: 'Sohbet A',
|
||||
checkpoint_revision: 9,
|
||||
current_model_label: 'Model A',
|
||||
limiting_target_id: null,
|
||||
target_budgets: null,
|
||||
active_summary_count: 0,
|
||||
summary_tokens: null,
|
||||
summary_token_measurement: null,
|
||||
last_operation_id: null,
|
||||
last_operation_state: null,
|
||||
last_operation_error: null,
|
||||
compact_attempts: []
|
||||
}
|
||||
]
|
||||
mount()
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Şimdi sıkıştır' }))
|
||||
await waitFor(() => expect(writes).toHaveLength(1))
|
||||
expect(writes[0]).toMatchObject({
|
||||
kind: 'context_compact_view',
|
||||
view_handle: 'opaque-view-a',
|
||||
expected_checkpoint_revision: 9
|
||||
})
|
||||
expect(writes[0]).not.toHaveProperty('thread_id')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user