diff --git a/docs/environment-variables.rst b/docs/environment-variables.rst index 3800b30ab..3d8923526 100644 --- a/docs/environment-variables.rst +++ b/docs/environment-variables.rst @@ -2178,7 +2178,7 @@ .. note:: **הפריימר לסוכן** (``GET /api/agent/primer``) אינו מוסיף אף משתנה סביבה. - תקרת ה-8KB ומשך ה-cache (60 שניות) הם קבועים ב-``mcp_server/primer.py`` + תקרת ה-24KB ומשך ה-cache (60 שניות) הם קבועים ב-``mcp_server/primer.py`` במכוון — התקרה היא תקציב מול חלון ההקשר של המודל ולא כוונון תפעולי, ומצב הכשל של העלאתה בלתי-נראה (הקשר נאכל בשקט). שינוי דורש PR. תוכן הפריימר נערך מהדפדפן ב-``/settings`` ונשמר ב-``user_preferences``, לא ב-ENV. diff --git a/docs/mcp-server.rst b/docs/mcp-server.rst index 44e43dafe..9024557b8 100644 --- a/docs/mcp-server.rst +++ b/docs/mcp-server.rst @@ -161,7 +161,7 @@ OAuth 2.1) וגם מול **Claude Code / Claude Desktop** (טוקן אישי). ושמות קבצים בלי מסגור הם רעש ולא פריימר. * - ``401`` - אין טוקן, או שהטוקן בוטל/פג. - * - תקרת 8KB + * - תקרת 24KB - חריגה גורמת לחיתוך + שורה שמודיעה על כך; **לעולם לא שגיאה**, כי פריימר חתוך עדיף על פריימר חסר. שורת המצב מקבלת מקום שמור ולא נדחפת החוצה. * - Cache @@ -172,7 +172,7 @@ OAuth 2.1) וגם מול **Claude Code / Claude Desktop** (טוקן אישי). ``sk-``, JWT, ``KEY=...`` וכו') לפני ההחזרה — גם אם הודבקו לשדה בטעות. ערכי משתני סביבה של השרת אינם נכנסים לגוף כלל. -התקרה וה-TTL הם **קבועים בקוד** ולא משתני סביבה, במכוון: ה-8KB אינו כוונון +התקרה וה-TTL הם **קבועים בקוד** ולא משתני סביבה, במכוון: התקרה אינה כוונון תפעולי אלא תקציב מול חלון ההקשר של המודל. אילו היה משתנה סביבה, מצב הכשל של העלאתו היה בלתי-נראה — הקשר נאכל בשקט בלי שאף אחד מתריע. שינוי דורש PR. @@ -188,10 +188,10 @@ OAuth 2.1) וגם מול **Claude Code / Claude Desktop** (טוקן אישי). ~~~~~~~~~~~~~ ``/settings`` → הכרטיס **"הוראות לסוכן"**. השדה מציג מונה בייטים חי ומתריע -כשמתקרבים לתקרה (מ-7KB) וכשחוצים אותה — כדי שחריגה תיתפס בכתיבה, ולא רק דרך +כשמתקרבים לתקרה (מ-21KB) וכשחוצים אותה — כדי שחריגה תיתפס בכתיבה, ולא רק דרך שורת חיתוך שמופיעה בגוף שרק הסוכן קורא. -האחסון (16KB תווים) רחב מההגשה (8KB) בכוונה: החיתוך הוא החלטה של ההגשה ולא +האחסון (48KB תווים) רחב מההגשה (24KB) בכוונה: החיתוך הוא החלטה של ההגשה ולא של האחסון, כדי שלא יימחק טקסט שהמשתמש כתב. הסודות נשמרים כפי שהם ומסוננים רק בהגשה — אחרת המשתמש היה פותח את ההגדרות ומוצא ``***REDACTED***`` בלי לדעת מה נמחק לו. diff --git a/mcp_server/README.md b/mcp_server/README.md index 91d1510fb..254689b8f 100644 --- a/mcp_server/README.md +++ b/mcp_server/README.md @@ -108,7 +108,7 @@ curl -sS -H "Authorization: Bearer $CODEKEEPER_PAT" \ - **תוכן:** שדה "הוראות לסוכן" מעמוד ההגדרות בוובאפ, ואחריו שורת מצב קצרה עם שלושת הקבצים האחרונים שנשמרו ומתי. - **`204`** כששדה ההוראות ריק (ולא `200` עם גוף ריק). **`401`** בלי טוקן תקין. -- **תקרת 8KB:** חריגה ⇒ חיתוך + שורה שמודיעה על כך. לעולם לא שגיאה. +- **תקרת 24KB:** חריגה ⇒ חיתוך + שורה שמודיעה על כך. לעולם לא שגיאה. - **Cache 60 שניות** לכל משתמש (`Cache-Control: private, max-age=60`). - **סינון סודות** על כל הגוף לפני ההחזרה. @@ -262,7 +262,7 @@ Claude.ai → /authorize → provider יוצר txn → הפניה ל-webapp /oau | `oauth_provider.py` | מימוש חוזה ה‑OAuth של ה‑SDK (כולל PAT מאוחד) | | `oauth_identity.py` | חתימת/אימות זהות HMAC (משותף עם הוובאפ) | | `oauth_routes.py` | מסך ה‑consent + הנפקת code | -| `primer.py` | `GET /api/agent/primer` — פריימר טקסט לסוכן (8KB, cache 60ש׳, סינון סודות) | +| `primer.py` | `GET /api/agent/primer` — פריימר טקסט לסוכן (24KB, cache 60ש׳, סינון סודות) | | `server.py` | חיווט FastMCP: כלים + OAuth + ASGI | | `app.py` | נקודת כניסה: בוחר PAT/OAuth לפי ENV | diff --git a/mcp_server/primer.py b/mcp_server/primer.py index 559fda777..3715e6e43 100644 --- a/mcp_server/primer.py +++ b/mcp_server/primer.py @@ -10,14 +10,14 @@ שהאפליקציה מוגנת: במצב OAuth (הפרודקשן) ה-``PATAuthMiddleware`` לא מותקן בכלל, וה-SDK עוטף רק את ה-mount של ``/mcp`` — ראוט שנרשם ידנית יוצא ציבורי. ראו את ה-docstring של ``mcp_server.auth``. -* **תקרת 8KB.** חורגים ⇒ חותכים ומוסיפים שורה שאומרת שנחתך. לעולם לא שגיאה: +* **תקרת 24KB.** חורגים ⇒ חותכים ומוסיפים שורה שאומרת שנחתך. לעולם לא שגיאה: פריימר חתוך עדיף על פריימר חסר. * **60 שניות cache.** נקרא בפתיחת כל סשן, ולכן אסור לו לגעת בשאילתה כבדה. * **סינון סודות.** הפריימר עובר דרך המודל, ולכן הוא מסונן בכל מקרה — גם אם המשתמש הדביק מפתח לשדה ההוראות בטעות. ערכי משתני סביבה של השרת לא נכנסים לגוף כלל: אין כאן שום קריאה ל-``os.environ``. -התקרה וה-TTL הם **קבועים בקוד** ולא משתני סביבה, במכוון. ה-8KB אינו כוונון +התקרה וה-TTL הם **קבועים בקוד** ולא משתני סביבה, במכוון. התקרה אינה כוונון תפעולי אלא תקציב מול חלון ההקשר של המודל, שמתחלק עם כל השאר בסשן; אילו היה משתנה סביבה, מצב הכשל של העלאתו היה בלתי-נראה (הקשר נאכל בשקט, בלי שאף אחד מתריע). שינוי דורש PR — וזה החיכוך הנכון. @@ -43,7 +43,7 @@ PRIMER_PATH = "/api/agent/primer" #: תקרת גודל הגוף בבייטים. קבוע במכוון — ראו את ה-docstring של המודול. -MAX_BYTES = 8 * 1024 +MAX_BYTES = 24 * 1024 #: משך ה-cache בשניות. CACHE_TTL_SECONDS = 60 @@ -64,10 +64,10 @@ #: סף האזהרה שה-UI בוובאפ מציג. חי כאן כי ``MAX_BYTES`` הוא מקור האמת שלו — #: ``webapp.routes.settings_routes`` משכפל את שניהם (שני שירותים נפרדים, בלי #: ייבוא ביניהם) ו-``test_mcp_primer`` מוודא שהערכים לא התפצלו. -WARN_BYTES = 7 * 1024 +WARN_BYTES = 21 * 1024 _TRUNCATION_NOTICE = ( - "\n[הפריימר נחתך כאן: ההוראות חורגות מתקרת 8KB. " + f"\n[הפריימר נחתך כאן: ההוראות חורגות מתקרת {MAX_BYTES // 1024}KB. " "קצרו אותן בהגדרות CodeKeeper — ההמשך לא נשלח לסוכן.]" ) diff --git a/tests/test_mcp_primer.py b/tests/test_mcp_primer.py index 75642317d..43c7a81f7 100644 --- a/tests/test_mcp_primer.py +++ b/tests/test_mcp_primer.py @@ -1,6 +1,6 @@ """טסטים ל-``GET /api/agent/primer`` (mcp_server/primer.py). -מכסה את ארבעת האילוצים של האנדפוינט: אימות, תקרת 8KB, cache של 60 שניות, +מכסה את ארבעת האילוצים של האנדפוינט: אימות, תקרת 24KB, cache של 60 שניות, וסינון סודות — וגם את מקרי הקצה של הגוף (204 בהוראות ריקות, אין שורת מצב כשאין קבצים). """ @@ -207,7 +207,7 @@ def recent_files(self, user_id, *, limit=3): assert "ההוראות שלי" in resp.text -# ---------------------------------------------------------------- תקרת 8KB +# --------------------------------------------------------------- תקרת 24KB def test_oversized_primer_is_truncated_not_rejected(): backend = _FakeBackend("א" * 20000, files=[{"file_name": "a.py", "saved_at": NOW}]) resp = _client(backend).get("/api/agent/primer", headers=_auth()) @@ -392,6 +392,7 @@ def test_webapp_limits_match_the_primer_limits(): """ flask = pytest.importorskip("flask") # noqa: F841 from webapp.routes.settings_routes import ( + AGENT_INSTRUCTIONS_MAX_CHARS, AGENT_INSTRUCTIONS_WARN_BYTES, AGENT_PRIMER_MAX_BYTES, ) @@ -400,6 +401,24 @@ def test_webapp_limits_match_the_primer_limits(): assert AGENT_INSTRUCTIONS_WARN_BYTES == WARN_BYTES assert WARN_BYTES < MAX_BYTES # אחרת האזהרה מגיעה רק אחרי החריגה + # האחסון רחב מההגשה — אינווריאנט מוצהר ב-``docs/mcp-server.rst``, שנועד + # לכך שהמשתמש לא יאבד טקסט שכתב: החיתוך הוא החלטה של ההגשה ולא של + # השמירה. ההשוואה היא תווים מול בייטים, ולכן המקרה הקובע הוא ASCII — + # שם תו הוא בייט. בעברית תו הוא שני בייטים והמרווח גדול פי שניים. + assert AGENT_INSTRUCTIONS_MAX_CHARS >= AGENT_PRIMER_MAX_BYTES + + +def test_the_truncation_notice_quotes_the_actual_cap(): + """הודעת החיתוך נגזרת מ-``MAX_BYTES`` ולא מוקלדת. + + ההודעה הזו היא הדבר היחיד שהסוכן רואה כשהפריימר נחתך, והיא מצטטת את + התקרה במספר. כשהיא הייתה מוקלדת ("8KB"), שינוי התקרה היה משאיר אותה + משקרת בשקט — בדיוק הדפוס של ערך משוכפל שמתפצל מהמקור. + """ + from mcp_server.primer import _TRUNCATION_NOTICE + + assert f"{MAX_BYTES // 1024}KB" in _TRUNCATION_NOTICE + def test_204_is_cached_too(): """גם "אין פריימר" הוא תשובה — אסור לה לפגוע במסד בכל פתיחת סשן.""" diff --git a/webapp/routes/settings_routes.py b/webapp/routes/settings_routes.py index cef77d4ad..b9fed613b 100644 --- a/webapp/routes/settings_routes.py +++ b/webapp/routes/settings_routes.py @@ -321,14 +321,14 @@ def theme_gallery(): # תקרת האחסון של "הוראות לסוכן". גבוהה מתקרת ההגשה בכוונה: החיתוך הוא החלטה # של ההגשה ולא של האחסון, כדי שהמשתמש לא יאבד טקסט שכתב. -AGENT_INSTRUCTIONS_MAX_CHARS = 16 * 1024 +AGENT_INSTRUCTIONS_MAX_CHARS = 48 * 1024 # תקרת הגוף שהפריימר מגיש בפועל וסף האזהרה ב-UI. מקור האמת הוא # ``mcp_server.primer`` (MAX_BYTES / WARN_BYTES); הערכים משוכפלים לכאן כי # הוובאפ ושירות ה-MCP הם שני שירותים נפרדים ואין ביניהם ייבוא. # ``tests/test_mcp_primer.py`` נכשל אם הזוגות מתפצלים. -AGENT_PRIMER_MAX_BYTES = 8 * 1024 -AGENT_INSTRUCTIONS_WARN_BYTES = 7 * 1024 +AGENT_PRIMER_MAX_BYTES = 24 * 1024 +AGENT_INSTRUCTIONS_WARN_BYTES = 21 * 1024 def _agent_instructions_user_id(): @@ -435,7 +435,7 @@ def api_update_agent_instructions(): { "ok": True, "bytes": size, - # ההגשה חותכת ב-8KB. מחזירים את הדגל כדי שה-UI יוכל להתריע מיד + # ההגשה חותכת ב-24KB. מחזירים את הדגל כדי שה-UI יוכל להתריע מיד # אחרי שמירה, ולא רק דרך שורה שרק הסוכן רואה. "will_truncate": size > AGENT_PRIMER_MAX_BYTES, } diff --git a/webapp/templates/settings.html b/webapp/templates/settings.html index 779c46339..a40060c66 100644 --- a/webapp/templates/settings.html +++ b/webapp/templates/settings.html @@ -457,9 +457,9 @@

const ENDPOINT = '/api/settings/agent-instructions'; // ברירות מחדל; השרת מחזיר את הערכים האמיתיים ב-GET כדי שלא יתפצלו משני צדדים. - let MAX_BYTES = 8 * 1024; - let WARN_BYTES = 7 * 1024; - let MAX_CHARS = 16 * 1024; + let MAX_BYTES = 24 * 1024; + let WARN_BYTES = 21 * 1024; + let MAX_CHARS = 48 * 1024; let saved = ''; const encoder = new TextEncoder();