feat(codex-link): AITURK work routing setting and beta.40
Server CI and Windows package / verify (push) Successful in 2m42s
Server CI and Windows package / windows-package (push) Successful in 6m21s

- 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:
yilsem committed 2026-10-15 02:18:18 +03:00
1 parent a7ba300448
commit 557e619499
16 files changed
+388 -26

No files matched your search

@@ -0,0 +1,82 @@
import fs from 'node:fs'
import path from 'node:path'
/** "AITURK iş hattı" choice, stored in the Codex bridge's `bridge.json` under `aiturk_hat`.
* The bridge re-reads the field when the file changes, so no restart is needed. */
export type IsHattiSecim = 'onerilen' | 'buyuk' | 'kapali' | 'ozel'
export interface IsHattiSettings {
secim: IsHattiSecim
talimat: string
}
const SECIMLER: IsHattiSecim[] = ['onerilen', 'buyuk', 'kapali', 'ozel']
const MAX_TALIMAT = 20000
export function readIsHatti(raw: null | string): IsHattiSettings {
try {
const hat = raw ? JSON.parse(raw)?.aiturk_hat : null
if (!hat || typeof hat !== 'object') {
return { secim: 'onerilen', talimat: '' }
}
const talimat = typeof hat.talimat === 'string' ? hat.talimat : ''
if (SECIMLER.includes(hat.secim)) {
return { secim: hat.secim, talimat }
}
// Written before this screen existed: same meaning the bridge gives it.
return { secim: hat.enabled === false ? 'kapali' : talimat.trim() ? 'ozel' : 'onerilen', talimat }
} catch {
return { secim: 'onerilen', talimat: '' }
}
}
export function writeIsHatti(raw: null | string, value: unknown): string {
const v = value as Partial<IsHattiSettings> | null
if (!v || !SECIMLER.includes(v.secim as IsHattiSecim)) throw new Error('Invalid choice')
const talimat = typeof v.talimat === 'string' ? v.talimat : ''
if (talimat.length > MAX_TALIMAT) throw new Error('Instruction too long')
if (v.secim === 'ozel' && !talimat.trim()) throw new Error('Custom instruction is empty')
let config: Record<string, unknown> = {}
try {
const parsed = raw ? JSON.parse(raw) : {}
config = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
} catch {
throw new Error('bridge.json is not valid JSON')
}
const old = config.aiturk_hat && typeof config.aiturk_hat === 'object' ? (config.aiturk_hat as Record<string, unknown>) : {}
const hat: Record<string, unknown> = { ...old, secim: v.secim, talimat }
delete hat.enabled
return `${JSON.stringify({ ...config, aiturk_hat: hat }, null, 2)}\n`
}
export function createIsHattiStore(directory: string) {
const file = path.join(directory, 'bridge.json')
const read = () => (fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null)
return {
get: (): IsHattiSettings => readIsHatti(read()),
save(value: unknown): IsHattiSettings {
const text = writeIsHatti(read(), value)
fs.mkdirSync(directory, { recursive: true })
const tmp = `${file}.${process.pid}.tmp`
try {
fs.writeFileSync(tmp, text, 'utf8')
fs.renameSync(tmp, file)
} finally {
if (fs.existsSync(tmp)) fs.unlinkSync(tmp)
}
return readIsHatti(text)
}
}
}