From 1f13b86e5fd8e06525a1fd05bbfa49f052447488 Mon Sep 17 00:00:00 2001 From: yilsem Date: Tue, 20 Oct 2026 21:02:45 +0300 Subject: [PATCH] fix(context): preserve summary details and unify IDE release branches Use the complete target budget, reject omitted concrete references, rebuild deficient checkpoints, and run both branches through Linux CI with an exact-branch parity gate. IDE beta.45, bridge24. --- .gitea/workflows/ci.yml | 168 ++++++++++-------- apps/desktop/assets/codex-bridge/README.md | 11 +- apps/desktop/assets/codex-bridge/bridge.py | 2 +- .../assets/codex-bridge/compression_engine.py | 3 + .../assets/codex-bridge/gecis_context.py | 51 +++++- .../codex-bridge/test_summary_coverage.py | 64 +++++++ apps/desktop/electron/codex-link-config.ts | 2 +- .../src/app/settings/compression-settings.tsx | 3 +- 8 files changed, 223 insertions(+), 81 deletions(-) create mode 100644 apps/desktop/assets/codex-bridge/test_summary_coverage.py diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 210f49d..b9f0687 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,24 +1,8 @@ -# AITURK IDE ajan CI (kart turkservis-20261007-1421-2). -# -# Bu depo bir yukari akis catalidir; .github/workflows altindaki GitHub is akislari Gitea'da kosmaz. .gitea/workflows -# klasoru var oldugu surece Gitea yalniz buradaki is akislarini okur. -# -# Ne kosar: korumali talimat dosyasi onay kapisinin testleri (insana sorulamayan kosuda aninda BLOCKED, gec onayin -# uygulanmamasi, yolo ile kapinin acilmamasi) ve dosya yazma guvenligi testleri. Kosucu Windows'tur (etiket -# windows-latest; 7 Ekim 2026'da cevrimici kosucu hermes-laptop-windows): urunun hedefi Windows x64'tur ve hata -# Windows asama kosusunda gorulmustur. -# -# Ne sayilmaz (durustluk notu): tests/tools/test_file_write_safety.py icindeki 7 test Windows'ta bu degisiklikten -# bagimsiz olarak gecemez; yukari akis bunlari POSIX icin yazmistir (/etc, /private/etc, /boot yollari; yonetici -# ayricaligi isteyen symlink; POSIX stat alanlari). Asagida adlariyla deselect edilir; degismemis 58dbb4523b -# commit'inde de ayni 7 test kaliyordu (7 Ekim 2026 olcumu). Tam yukari akis test takimi burada KOSMAZ. -name: ci +name: Server CI and Windows package on: push: - branches: - - aiturk/main - - fix/ide-windows-vcruntime + branches: [aiturk/main, fix/ide-windows-vcruntime] workflow_dispatch: concurrency: @@ -29,64 +13,100 @@ permissions: contents: read jobs: - approval-gate-windows: - runs-on: windows-latest + verify: + runs-on: ubuntu-24.04 timeout-minutes: 40 + env: + NODE_OPTIONS: --max-old-space-size=2560 + PYTHONDONTWRITEBYTECODE: '1' steps: - - name: Kaynagi al - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: - github-server-url: https://gitea.twinpay.one - - - name: Ortami kur - shell: powershell + github-server-url: https://gitea.turkservis.online + - name: Require main and Windows release to share the exact source run: | - $ErrorActionPreference = 'Stop' - "kosucu: $env:COMPUTERNAME / $env:USERNAME" - "checkout SHA: $(git rev-parse HEAD) / beklenen: $env:GITHUB_SHA" - $py = $null - $launcher = Get-Command py -ErrorAction SilentlyContinue - if ($launcher) { $py = (& $launcher.Source -3 -c 'import sys; print(sys.executable)').Trim() } - if (-not $py) { - $cmd = Get-Command python -ErrorAction SilentlyContinue - if ($cmd -and $cmd.Source -notlike '*\WindowsApps\*') { $py = $cmd.Source } - } - if (-not $py) { throw 'Python bulunamadi' } - "onyukleme python: $py" - & $py -m pip install --quiet --disable-pip-version-check --target "$env:RUNNER_TEMP\uvboot" uv - if ($LASTEXITCODE -ne 0) { throw 'uv kurulamadi' } - $env:PYTHONPATH = "$env:RUNNER_TEMP\uvboot" - & $py -m uv venv --python 3.11 "$env:RUNNER_TEMP\ci-venv" - if ($LASTEXITCODE -ne 0) { throw 'venv olusturulamadi' } - & $py -m uv pip install --python "$env:RUNNER_TEMP\ci-venv\Scripts\python.exe" -e . pytest - if ($LASTEXITCODE -ne 0) { throw 'bagimliliklar kurulamadi' } - $env:PYTHONPATH = '' - & "$env:RUNNER_TEMP\ci-venv\Scripts\python.exe" --version - - - name: Onay kapisi testleri - shell: powershell + set -eu + main_sha="$(git ls-remote origin refs/heads/aiturk/main | cut -f1)" + release_sha="$(git ls-remote origin refs/heads/fix/ide-windows-vcruntime | cut -f1)" + if [ "$main_sha" != "$GITHUB_SHA" ] || [ "$release_sha" != "$GITHUB_SHA" ]; then + echo "Both IDE branches must be updated together to the same merged commit." + echo "Use one atomic push; refusing to publish a divergent branch." + exit 1 + fi + - name: Exact source and Linux test environment run: | - $ErrorActionPreference = 'Continue' - $env:PYTHONDONTWRITEBYTECODE = '1' - $env:PYTHONUTF8 = '1' - $venv = "$env:RUNNER_TEMP\ci-venv\Scripts\python.exe" - $f = 'tests/tools/test_file_write_safety.py' - $sayilmayan = @( - "${f}::TestCheckSensitivePathMacOSBypass::test_etc_hosts_blocked", - "${f}::TestCheckSensitivePathMacOSBypass::test_private_etc_hosts_blocked", - "${f}::TestCheckSensitivePathMacOSBypass::test_private_etc_ssh_config_blocked", - "${f}::TestCheckSensitivePathMacOSBypass::test_private_var_blocked", - "${f}::TestCheckSensitivePathMacOSBypass::test_boot_still_blocked", - "${f}::TestAtomicWrite::test_patch_routes_through_atomic_write", - "${f}::TestProtectedInstructionFiles::test_symlink_to_protected_file_is_gated" - ) - $deselect = @() - foreach ($t in $sayilmayan) { $deselect += '--deselect'; $deselect += $t } - "CI OZETI: Windows'ta sayilmayan (deselect) test sayisi: $($sayilmayan.Count)" - $sayilmayan | ForEach-Object { " sayilmadi: $_" } - & $venv -m pytest tests/tools/test_protected_instruction_unattended.py $f ` - tests/tools/test_computer_use_approval_isolation.py tests/cli/test_cli_approval_ui.py ` - tests/cli/test_single_query_session_finalize.py @deselect -q -p no:cacheprovider -rf - $kod = $LASTEXITCODE - "pytest cikis kodu: $kod" - exit $kod + set -eu + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + apt-get update + apt-get install -y --no-install-recommends ripgrep + rg --version + python3 -m venv "$RUNNER_TEMP/bootstrap" + "$RUNNER_TEMP/bootstrap/bin/pip" install uv + "$RUNNER_TEMP/bootstrap/bin/uv" venv --python 3.11 "$RUNNER_TEMP/ci-venv" + "$RUNNER_TEMP/bootstrap/bin/uv" pip install --python "$RUNNER_TEMP/ci-venv/bin/python" -e . pytest zstandard + - name: Approval gate and file safety on the server + run: | + "$RUNNER_TEMP/ci-venv/bin/python" -m pytest tests/tools/test_protected_instruction_unattended.py tests/tools/test_file_write_safety.py tests/tools/test_computer_use_approval_isolation.py tests/cli/test_cli_approval_ui.py tests/cli/test_single_query_session_finalize.py -q -p no:cacheprovider -rf + - name: Codex bridge regression tests + run: '"$RUNNER_TEMP/ci-venv/bin/python" -m unittest discover -s apps/desktop/assets/codex-bridge' + - name: Managed Python update preservation + run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest tests/test_install_ps1_reuse_healthy_venv.py tests/test_install_ps1_python_fallback_venv.py -q -p no:cacheprovider -rf' + - name: Project scoped and bounded search regressions + run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest tests/tools/test_search_project_scope.py tests/tools/test_search_files_cpu_windows.py tests/tools/test_search_zero_match_and_multipath.py tests/tools/test_search_giant_line_containment.py tests/tools/test_search_budget_truncation.py -q -p no:cacheprovider -rf' + - name: Desktop browser control lane (AITURK Chrome) + run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest tests/gateway/test_browser_control_local_lane.py tests/gateway/test_browser_control_broker.py tests/gateway/test_browser_control_broker_hardening.py tests/gateway/test_browser_control_cloud.py tests/tools/test_browser_extension_router.py tests/tools/test_browser_extension_router_wiring.py -q -p no:cacheprovider -rf' + - name: Native watch and speech mux pytest regressions + run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest apps/desktop/assets/codex-bridge/test_bekci_watch.py apps/desktop/assets/codex-bridge/test_native_speech_mux.py apps/desktop/assets/codex-bridge/test_live_limits.py -q -p no:cacheprovider -rf' + - name: Mobile and desktop startup regression tests + run: | + npm install --prefix "$RUNNER_TEMP/mobile-tests" --no-save --ignore-scripts --no-audit --no-fund --legacy-peer-deps vitest@4.1.10 ws@8.21.1 + ln -s "$RUNNER_TEMP/mobile-tests/node_modules" node_modules + node node_modules/vitest/vitest.mjs run --root apps/desktop --config vitest.mobile.config.ts --maxWorkers=2 + + windows-package: + if: github.ref == 'refs/heads/fix/ide-windows-vcruntime' + needs: verify + runs-on: ubuntu-24.04 + timeout-minutes: 60 + # Windows packaging runs on Linux/Wine in the new server. No laptop runner. + container: electronuserland/builder@sha256:b226b27b1a7e95ad25eb3d6414ea90c56c04eb6d8329967f4d721dce50b989ff + env: + NODE_OPTIONS: --max-old-space-size=2560 + CSC_IDENTITY_AUTO_DISCOVERY: 'false' + steps: + - uses: actions/checkout@v4 + with: + github-server-url: https://gitea.turkservis.online + - name: Install locked source dependencies + run: npm ci --no-audit --no-fund + - name: Chrome extension, agent control and bridge tests + run: node --test apps/chrome/tests/agent.test.mjs apps/chrome/tests/bridge.test.mjs apps/chrome/tests/panel.test.mjs + - name: Install Windows launcher cross compiler and framework reference + run: | + set -eu + apt-get update + apt-get install -y --no-install-recommends mono-mcs=6.8.0.105+dfsg-3.2 libmono-system-web-extensions4.0-cil=6.8.0.105+dfsg-3.2 + - name: Compile Windows launcher from source and verify failure gate + run: node --test apps/desktop/scripts/build-native-watch-launcher.test.mjs + - name: Native dependency staging regression tests + run: npx vitest run --root apps/desktop scripts/stage-native-deps.test.mjs --maxWorkers=2 + - name: Codex connection settings regression tests + run: npx vitest run --root apps/desktop electron/codex-link.test.ts electron/codex-link-config.test.ts electron/staged-transition.test.ts electron/compression-settings.test.ts src/app/settings/codex-link-settings.test.tsx src/app/settings/staged-transition-settings.test.tsx src/app/settings/compression-settings.test.tsx --maxWorkers=2 + - name: Build and package Windows x64 + run: | + set -eu + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + npm run build --workspace=apps/desktop + npm run builder --workspace=apps/desktop -- --win nsis zip --x64 --publish never + git diff --exit-code + cd apps/desktop/release + sha256sum *.exe *.zip > SHA256SUMS + - name: Preserve exact-source Windows package + uses: actions/upload-artifact@v3 + with: + name: aiturk-ide-windows-${{ github.sha }} + if-no-files-found: error + path: | + apps/desktop/release/*.exe + apps/desktop/release/*.zip + apps/desktop/release/SHA256SUMS diff --git a/apps/desktop/assets/codex-bridge/README.md b/apps/desktop/assets/codex-bridge/README.md index 8e2b590..9a713aa 100644 --- a/apps/desktop/assets/codex-bridge/README.md +++ b/apps/desktop/assets/codex-bridge/README.md @@ -27,7 +27,7 @@ Run: `python bridge.py --home --hermes-home `. The IDE wr Tests: `python -m unittest discover` in this directory. -## Version 23: portable model switches (IDE beta.45) +## Version 24: portable model switches and summary coverage (IDE beta.45) Cross-provider requests replace encrypted reasoning and every typed encrypted peer-message part with a short visible omission note. Ciphertext is never @@ -45,6 +45,15 @@ provider, cache policy and compressor without resetting conversation history; `website/docs/user-guide/configuring-models.md` documents in-session `/model`. Native voice routing is unchanged. +The compression target is a maximum, not a minimum length. All of the available +target budget is offered to the summarizer; compression level controls prose +density and no longer multiplies that budget down a second time. Numbered +decision identifiers, file references and numeric settings must survive the +summary before it is accepted or cached. An incomplete provider summary tries +the configured fallback; a deficient old checkpoint is ignored and rebuilt. +These literal checks detect concrete omissions; they do not prove semantic +equivalence of every sentence. + ## Version 19: decision stages (14 October 2026) The decision model (Jev or another System One compatible service such as Celeris) can now be asked at diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index e722bb3..774fc24 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -35,7 +35,7 @@ import gecis_context import terminal_stream HERE = os.path.dirname(os.path.abspath(__file__)) -SURUM = "23" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION) +SURUM = "24" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION) try: with open(os.path.abspath(__file__), "rb") as _f: KOD = hashlib.sha256(_f.read()).hexdigest()[:8] diff --git a/apps/desktop/assets/codex-bridge/compression_engine.py b/apps/desktop/assets/codex-bridge/compression_engine.py index 0855adb..5e05348 100644 --- a/apps/desktop/assets/codex-bridge/compression_engine.py +++ b/apps/desktop/assets/codex-bridge/compression_engine.py @@ -142,6 +142,7 @@ def call(handler, module, slug, source, budget, settings, deadline, depth=0): text = ozet_kopru.sse_metni(bytes(raw)) if not text or len(text.strip()) < 80: raise gc.TransferError('Sıkıştırıcı tamamlanmış bir özet döndürmedi') + gc.require_summary_coverage(chunk, text) results.append(text) combined = '\n\n'.join(results) if not combined: @@ -211,6 +212,8 @@ def prepare(handler, module, payload, checkpoints): protected, old, recent = gc.split_history(req.get('input') or [], settings['keepTurns']) checkpoints.begin(scope, old, settings) saved = checkpoints.get(scope, old, settings) if old else None + if saved and gc.summary_missing(old[:saved[0]], saved[1]): + saved = None effective = dict(req, input=protected + [ozet_kopru._ozet_mesaji(saved[1])] + old[saved[0]:] + recent) if saved else req if not compact and gc.tokens(effective) <= trigger: return {'request': effective, 'window': window} diff --git a/apps/desktop/assets/codex-bridge/gecis_context.py b/apps/desktop/assets/codex-bridge/gecis_context.py index 7081ce8..3e12e04 100644 --- a/apps/desktop/assets/codex-bridge/gecis_context.py +++ b/apps/desktop/assets/codex-bridge/gecis_context.py @@ -7,11 +7,11 @@ must not enter the budget preparation path. import copy import hashlib import json +import re import threading from collections import OrderedDict import ozet_kopru -import compression_settings PREFIXES = {"custom_tool_call": "ctc", "custom_tool_call_output": "ctco", "function_call": "fc", "function_call_output": "fco"} @@ -20,6 +20,46 @@ PEER_OMITTED = "[Aktarım notu: Burada okunamayan şifreli bir alt ajan iletisi REASONING_OMITTED = "[Aktarım notu: Şifreli model düşünme kaydı aktarılmadı.]" +def summary_missing(source, summary): + """Check literal decisions, file paths and numeric settings, never secrets. + + This is a loss detector for concrete references, not a semantic proof of + every sentence. Missing references must not enter a durable checkpoint. + """ + if isinstance(source, str): + try: + source = json.loads(source) + except ValueError: + pass + def strings(value): + if isinstance(value, str): + yield value + elif isinstance(value, dict): + for child in value.values(): + yield from strings(child) + elif isinstance(value, list): + for child in value: + yield from strings(child) + text = '\n'.join(strings(source)).replace('\\', '/') + target = summary.replace('\\', '/').replace('`', '') + identifiers = set(re.findall(r'\b[A-Z][A-Z0-9]{0,23}(?:-[A-Z][A-Z0-9]{0,23}){0,3}-\d[A-Za-z0-9_-]{0,63}\b', text)) + identifiers = {x for x in identifiers if not re.match(r'(?:SECRET|TOKEN|APIKEY|PASSWORD|OTP)-', x)} + paths = set(re.findall(r'(? limit: raise TransferError("Hazırlanan özet hedef bütçesine sığmadı; eski geçmiş korundu.") diff --git a/apps/desktop/assets/codex-bridge/test_summary_coverage.py b/apps/desktop/assets/codex-bridge/test_summary_coverage.py new file mode 100644 index 0000000..81cc320 --- /dev/null +++ b/apps/desktop/assets/codex-bridge/test_summary_coverage.py @@ -0,0 +1,64 @@ +import copy +import tempfile +import unittest + +import compression_settings as cs +import gecis_context as gc + + +class SummaryCoverage(unittest.TestCase): + def test_long_noise_is_not_misclassified_as_one_giant_identifier(self): + gc.require_summary_coverage('A' * 60000 + 'KARAR-4827' + 'B' * 60000, + 'KARAR-4827: original decision preserved.') + + def test_partial_decision_ledger_is_rejected_and_full_ledger_is_accepted(self): + records = [f'DEC-{i:04d}: src/feature_{i:04d}/config.json limit_{i:04d}={30000+i} TEST-QA-{i:04d} NO-DELETE-{i:04d}' + for i in range(200)] + source = [{'role': 'user', 'content': '\n'.join(records)}] + with self.assertRaises(gc.TransferError): + gc.require_summary_coverage(source, '\n'.join(records[:95])) + gc.require_summary_coverage(source, '\n'.join(records)) + + def test_assignment_values_stay_associated_with_their_keys(self): + source = [{'content': 'limit_a=123 limit_b=456 src/a.py DEC-1'}] + self.assertGreater(gc.summary_missing(source, 'limit_a=456 limit_b=123 src/a.py DEC-1'), 0) + gc.require_summary_coverage(source, 'DEC-1: `src/a.py`; limit_a: 123; limit_b: 456') + + def test_secret_named_assignments_are_not_required_in_a_summary(self): + gc.require_summary_coverage([{'content': 'api_key=123456 password=7890 otp=1234'}], 'Secrets omitted.') + + def test_target_budget_is_not_multiplied_by_compression_level_again(self): + settings = dict(cs.DEFAULTS, keepTurns=1, triggerPercent=60, targetPercent=25, level='fazla') + req = {'input': [{'role': 'user', 'content': 'DEC-1 ' + 'x' * 60000}, + {'role': 'user', 'content': 'Keep this recent turn.'}]} + observed = [] + def summarize(source, budget): + observed.append(budget) + return 'DEC-1: All accepted decisions, paths, settings and outstanding work are retained. ' * 2 + prepared = gc.Preparer().prepare(req, 32768, summarize, settings=settings, automatic=True) + protected, _, recent = gc.split_history(req['input'], 1) + available = gc.input_budget(req, 32768, settings, True) - gc.tokens(dict(req, input=protected + recent)) - 256 + self.assertEqual(observed, [available]) + self.assertLessEqual(gc.tokens(prepared), 32768 * .25) + + def test_incomplete_old_checkpoint_is_rebuilt_without_overwriting_source(self): + settings = dict(cs.DEFAULTS, keepTurns=1) + req = {'input': [{'role': 'user', 'content': 'DEC-1 src/a.py limit_a=123 ' + 'x' * 60000}, + {'role': 'user', 'content': 'Latest'}]} + original = copy.deepcopy(req) + with tempfile.TemporaryDirectory() as home: + store = cs.Checkpoints(home) + old = gc.split_history(req['input'], 1)[1] + store.save('scope', old, settings, 'Incomplete old summary without the required details. ' * 3) + calls = [] + def summarize(source, budget): + calls.append(1) + return 'DEC-1 src/a.py limit_a=123. Accepted decision and all relevant constraints retained. ' * 2 + result = gc.Preparer().prepare(req, 32768, summarize, 'scope', settings=settings, checkpoints=store) + self.assertEqual(calls, [1]) + self.assertEqual(req, original) + gc.require_summary_coverage(old, gc.encoded(result)) + + +if __name__ == '__main__': + unittest.main() diff --git a/apps/desktop/electron/codex-link-config.ts b/apps/desktop/electron/codex-link-config.ts index ea8f72d..fa3a13e 100644 --- a/apps/desktop/electron/codex-link-config.ts +++ b/apps/desktop/electron/codex-link-config.ts @@ -27,7 +27,7 @@ export const CODEX_LINK_REFRESH_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama export const CODEX_LINK_MARKER = '# AITURK IDE Codex Baglama' // Must equal SURUM in assets/codex-bridge/bridge.py; a running bridge that reports // another version is restarted so an updated IDE never keeps an old bridge alive. -export const CODEX_BRIDGE_VERSION = '23' +export const CODEX_BRIDGE_VERSION = '24' export const CODEX_LINK_LINE = `openai_base_url = "${CODEX_LINK_ORIGIN}/backend-api/codex" ${CODEX_LINK_MARKER}` export const CODEX_LINK_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' diff --git a/apps/desktop/src/app/settings/compression-settings.tsx b/apps/desktop/src/app/settings/compression-settings.tsx index 1ecd4fd..f7c0319 100644 --- a/apps/desktop/src/app/settings/compression-settings.tsx +++ b/apps/desktop/src/app/settings/compression-settings.tsx @@ -63,7 +63,8 @@ export function CompressionSettingsView() { {draft.budgetMode === 'percent' ? numeric('transferPercent', t('Hedef model bağlamı (%)', 'Target model context (%)'), 10, 90) : numeric('maxTokens', t('Maksimum giriş tokenı', 'Maximum input tokens'), 1024, 2000000)} change('autoEnabled', v)} />} /> {numeric('triggerPercent', t('Başlama eşiği (%)', 'Trigger threshold (%)'), 20, 90)} - {numeric('targetPercent', t('Sıkıştırma sonrası hedef (%)', 'Target after compression (%)'), 10, 80)} + {numeric('targetPercent', t('Sıkıştırma sonrası üst sınır (%)', 'Maximum after compression (%)'), 10, 80)} +

{t('Bu bir üst sınırdır; tekrarlar azaldığında özet daha küçük olabilir. Sıkıştırma seviyesi anlatım yoğunluğunu belirler, bu sınırı ayrıca küçültmez.', 'This is an upper limit; removing repetition can produce a smaller summary. Compression level controls writing density without shrinking this budget again.')}

{numeric('timeoutSeconds', t('Sıkıştırıcı bekleme süresi (saniye)', 'Summarizer timeout (seconds)'), 1, 3600)}