feat(codex-link): AITURK work routing setting and beta.40
- Default routing rule (user decision, 15 Oct 2026): repeating/scheduled work goes to the AITURK IDE bot as a cron job; coding stays in Codex (itself or its subagents); 'Codex' or 'sen yap' keeps everything in Codex. - Settings > Codex Bağlama: choice (recommended / send large work / never / custom text) with a text box and restore-default; written to bridge.json aiturk_hat. - Bridge re-reads aiturk_hat when bridge.json changes: no restart. IDE apply keeps aiturk_hat instead of wiping it. - Bridge version 20, IDE 1.0.0-beta.40. Jev untouched.
This commit is contained in:
1 parent
a7ba300448
commit
557e619499
16 files changed
+388
-26
No files matched your search
@@ -12,6 +12,7 @@ import type { CodexLinkPatch, CodexLinkStatus } from '../../../electron/codex-li
|
||||
|
||||
import { EmptyState, ListRow, ListRowSkeleton, SettingsContent, SettingsSection } from './primitives'
|
||||
import { CompressionSettingsView } from './compression-settings'
|
||||
import { IsHattiSettingsView } from './is-hatti-settings'
|
||||
|
||||
type Tone = 'muted' | 'ok' | 'warn'
|
||||
|
||||
@@ -251,6 +252,7 @@ export function CodexLinkSettings() {
|
||||
{copy.restartNote}
|
||||
</p>
|
||||
</SettingsSection>
|
||||
<IsHattiSettingsView />
|
||||
<CompressionSettingsView />
|
||||
</SettingsContent>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
|
||||
import { I18nProvider } from '@/i18n/context'
|
||||
|
||||
import { IsHattiSettingsView } from './is-hatti-settings'
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop
|
||||
})
|
||||
|
||||
it('shows the custom text box only for custom text and saves the choice', async () => {
|
||||
const save = vi.fn(async (s: unknown) => s)
|
||||
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
|
||||
codexLink: { getIsHatti: async () => ({ secim: 'onerilen', talimat: '' }), setIsHatti: save }
|
||||
}
|
||||
render(<I18nProvider configClient={null} initialLocale="tr"><IsHattiSettingsView /></I18nProvider>)
|
||||
const select = await screen.findByRole('combobox', { name: 'İş dağıtımı' })
|
||||
expect(screen.queryByRole('textbox')).toBeNull()
|
||||
fireEvent.change(select, { target: { value: 'ozel' } })
|
||||
const box = screen.getByRole('textbox', { name: 'Codex’e eklenecek kural' })
|
||||
expect((screen.getByRole('button', { name: 'Kaydet' }) as HTMLButtonElement).disabled).toBe(true)
|
||||
fireEvent.change(box, { target: { value: 'kendi kuralım' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Kaydet' }))
|
||||
await waitFor(() => expect(save).toHaveBeenCalledWith({ secim: 'ozel', talimat: 'kendi kuralım' }))
|
||||
expect(await screen.findByText(/yeniden başlatma gerekmez/)).toBeTruthy()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Varsayılana dön' }))
|
||||
await waitFor(() => expect(save).toHaveBeenLastCalledWith({ secim: 'onerilen', talimat: 'kendi kuralım' }))
|
||||
})
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useI18n } from '@/i18n/context'
|
||||
import { Link } from '@/lib/icons'
|
||||
|
||||
import type { IsHattiSecim, IsHattiSettings } from '../../../electron/is-hatti-settings'
|
||||
|
||||
import { ListRow, SettingsSection } from './primitives'
|
||||
|
||||
const SECENEKLER: { value: IsHattiSecim; tr: string; en: string; aciklama: string; desc: string }[] = [
|
||||
{ value: 'onerilen', tr: 'Önerilen', en: 'Recommended', aciklama: 'Her gün, saat başı gibi tekrarlı işler AITURK IDE botuna zamanlanmış iş olarak gider. Kodlama işlerini Codex kendisi ya da kendi alt ajanlarıyla yapar.', desc: 'Repeating or scheduled work goes to the AITURK IDE bot. Codex keeps coding work and does it itself or with its own subagents.' },
|
||||
{ value: 'buyuk', tr: 'Büyük işleri AITURK’e gönder', en: 'Send large work to AITURK', aciklama: 'Eski davranış: birkaç adımlı ya da çok dosyalı işler AITURK’e gönderilir, küçük işleri Codex yapar.', desc: 'Previous behaviour: multi-step or multi-file work goes to AITURK; Codex does small tasks.' },
|
||||
{ value: 'kapali', tr: 'Hiçbir işi AITURK’e gönderme', en: 'Never send work to AITURK', aciklama: 'Codex’e iş dağıtımı hakkında hiçbir not eklenmez; her işi Codex yapar.', desc: 'No routing note is added; Codex does every task.' },
|
||||
{ value: 'ozel', tr: 'Kendi metnim', en: 'Custom text', aciklama: 'Codex’e eklenecek kuralı siz yazarsınız.', desc: 'You write the rule given to Codex.' }
|
||||
]
|
||||
|
||||
export function IsHattiSettingsView() {
|
||||
const api = window.hermesDesktop?.codexLink
|
||||
const { locale } = useI18n()
|
||||
const tr = locale === 'tr'
|
||||
const t = (a: string, b: string) => (tr ? a : b)
|
||||
const [draft, setDraft] = useState<IsHattiSettings | null>(null)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
const [failed, setFailed] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
api?.getIsHatti?.().then(
|
||||
next => active && setDraft(next),
|
||||
() => {
|
||||
if (active) {
|
||||
setFailed(true)
|
||||
setMessage(tr ? 'İş dağıtımı ayarı okunamadı.' : 'Could not load the work routing setting.')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
active = false
|
||||
}
|
||||
}, [api, tr])
|
||||
|
||||
if (!api?.getIsHatti || !api.setIsHatti) return null
|
||||
|
||||
const save = async (next: IsHattiSettings) => {
|
||||
setBusy(true)
|
||||
setMessage('')
|
||||
setFailed(false)
|
||||
|
||||
try {
|
||||
setDraft(await api.setIsHatti!(next))
|
||||
setMessage(t('Kaydedildi. Codex’in bir sonraki mesajından itibaren geçerli; yeniden başlatma gerekmez.', 'Saved. Applies from Codex’s next message; no restart needed.'))
|
||||
} catch {
|
||||
setFailed(true)
|
||||
setMessage(t('Kaydedilemedi. Kendi metninizi seçtiyseniz kutunun boş olmadığını kontrol edin.', 'Could not save. If you chose custom text, make sure it is not empty.'))
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
const secili = SECENEKLER.find(s => s.value === draft?.secim)
|
||||
|
||||
return (
|
||||
<SettingsSection icon={Link} title={t('AITURK iş hattı', 'AITURK work routing')}>
|
||||
<p className="text-sm text-(--ui-text-secondary)">
|
||||
{t('Codex masaüstü uygulamasının hangi işleri AITURK’e devredeceğini seçin. Kullanıcı “Codex” ya da “sen yap” derse hiçbir iş devredilmez.', 'Choose which work the Codex desktop app hands to AITURK. Saying “Codex” or “do it yourself” keeps every task in Codex.')}
|
||||
</p>
|
||||
{draft && (
|
||||
<>
|
||||
<ListRow
|
||||
action={
|
||||
<select
|
||||
aria-label={t('İş dağıtımı', 'Work routing')}
|
||||
className="min-h-11 max-w-full rounded-md border bg-(--ui-bg) p-2"
|
||||
disabled={busy}
|
||||
onChange={e => setDraft({ ...draft, secim: e.target.value as IsHattiSecim })}
|
||||
value={draft.secim}
|
||||
>
|
||||
{SECENEKLER.map(s => (
|
||||
<option key={s.value} value={s.value}>
|
||||
{tr ? s.tr : s.en}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
}
|
||||
description={secili ? (tr ? secili.aciklama : secili.desc) : undefined}
|
||||
title={t('İş dağıtımı', 'Work routing')}
|
||||
/>
|
||||
{draft.secim === 'ozel' && (
|
||||
<>
|
||||
<textarea
|
||||
aria-label={t('Codex’e eklenecek kural', 'Rule given to Codex')}
|
||||
className="min-h-40 w-full rounded-md border bg-(--ui-bg) p-2 text-sm"
|
||||
disabled={busy}
|
||||
onChange={e => setDraft({ ...draft, talimat: e.target.value })}
|
||||
placeholder={t('Örnek: Kodlama işlerini kendin yap. Her gün tekrarlanacak işleri {zamanla} satırıyla AITURK IDE botuna ver.', 'Example: Do coding yourself. Give daily work to the AITURK IDE bot with {zamanla}.')}
|
||||
value={draft.talimat}
|
||||
/>
|
||||
<p className="text-sm text-(--ui-text-secondary)">
|
||||
{t('Metinde {zamanla} yazarsanız AITURK IDE botuna zamanlanmış iş veren komut, {komut} yazarsanız işi AITURK’e gönderen komut yerine konur.', 'Write {zamanla} to insert the command that schedules work on the AITURK IDE bot, or {komut} for the command that sends work to AITURK.')}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<Button disabled={busy || (draft.secim === 'ozel' && !draft.talimat.trim())} onClick={() => void save(draft)}>
|
||||
{busy ? t('Kaydediliyor…', 'Saving…') : t('Kaydet', 'Save')}
|
||||
</Button>
|
||||
<Button disabled={busy} onClick={() => void save({ secim: 'onerilen', talimat: draft.talimat })} variant="secondary">
|
||||
{t('Varsayılana dön', 'Restore default')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{message && <p role={failed ? 'alert' : 'status'}>{message}</p>}
|
||||
</SettingsSection>
|
||||
)
|
||||
}
|
||||
Vendored
+3
@@ -3,6 +3,7 @@ import type { TranslucencyState } from '@hermes/shared/translucency'
|
||||
|
||||
import type { CodexLinkPatch, CodexLinkStatus } from '../electron/codex-link-types'
|
||||
import type { CompressionSettings, CompressionState } from '../electron/compression-settings-types'
|
||||
import type { IsHattiSettings } from '../electron/is-hatti-settings'
|
||||
import type { PoolLimits } from '../electron/pool-limits'
|
||||
import type { StagedTransitionPatch, StagedTransitionState } from '../electron/staged-transition-types'
|
||||
|
||||
@@ -344,6 +345,8 @@ declare global {
|
||||
codexLink?: {
|
||||
getCompression: () => Promise<CompressionState>
|
||||
setCompression: (settings: CompressionSettings) => Promise<CompressionState>
|
||||
getIsHatti?: () => Promise<IsHattiSettings>
|
||||
setIsHatti?: (settings: IsHattiSettings) => Promise<IsHattiSettings>
|
||||
getStagedTransition: () => Promise<StagedTransitionState>
|
||||
setStagedTransition: (patch: StagedTransitionPatch) => Promise<StagedTransitionState>
|
||||
get: () => Promise<CodexLinkStatus>
|
||||
|
||||
Reference in new issue
Block a user