פריסה ולקוחות
Control Center אינו תוכנה אחת. זוהי ארכיטקטורת לקוח דק: שרת ללא ממשק שמחזיק בנתונים שלכם וקבוצה של לקוחות שמרנדרים ממשק מעל חיבור RPC יחיד אליו. הבנת הפיצול הזה מסבירה מדוע דברים מסוימים רצים במקום שבו הם רצים ומה כל לקוח יכול ומה אינו יכול להגיע אליו.
השרת מחזיק בנתונים
קטע בשם “השרת מחזיק בנתונים”תהליך בשם cc_server מחזיק במסדי הנתונים — global.db קטן למצב
כלל-שרתי בתוספת קובץ SQLite אחד לכל סביבת עבודה,
שנפתחת רק בעת הצורך — ומריץ את העבודה ארוכת הטווח: מנועי פייפליין, מנגנוני
התאמת מצב, משטח הכלים של MCP ומאזין התזמור. כל מה שצריך לשרוד מעבר לחלון,
או ששני לקוחות אמורים לראות באופן זהה, חי שם.
שום לקוח לא פותח קובץ מסד נתונים בעצמו. במקום זאת כל לקוח מחזיק
RemoteRpcClient אחד ומבצע דרכו פעולות מאגר ומנויים. השרת מאמת את
החיבור מול מפתח מקדים משותף של מכשיר מצומד — ואז מפסיק להתעניין בחיבור.
הפעלה אינה כבולה לסביבת עבודה. המשגר חסר מצב: כל קריאה שהיקפה הוא
סביבת עבודה נושאת את ה-workspace_id שלה בארגומנטים שלה, וקריאה שמשמיטה
אותו נדחית.
זה מכוון, וזה מה שמאפשר לשני לקוחות על שרת אחד לשבת בסביבות עבודה שונות בלי “סביבת עבודה נוכחית” שהשרת מחזיק ושהם יכולים לחלוק עליה. הבידוד נאכף במקום זאת לכל קריאה, בארבעה שלבים:
- הפעולה מצהירה אם היקפה הוא סביבת עבודה; אם כן ולא הגיע
workspace_id, הקריאה נדחית. - המזהה הנקוב חייב להיות סביבת עבודה רשומה, שנבדקת לפני שמשהו פותח מסד
נתונים — אחרת מזהה לא מוכר היה יוצר בכל בקשה קובץ
workspace.dbרפאים וריק. מזהה שאינו נמצא מחזיר שגיאת “לא נמצא”. - ה-
WorkspaceRoleשל הקורא חייב לעמוד ברף המינימום של הפעולה, הנגזר מהסוג שלה: קריאה דורשת אורח, שינוי דורש חבר, פעולה הרסנית דורשת מנהל. - פעולה שחושפת תוכן מאגר דורשת בנוסף הרשאה ברמת המאגר, ברמה שהיא מצהירה
עליה —
none/read/review/write— כך שחברות בסביבת עבודה לעולם אינה מעניקה בשקט הרשאות עודפות על מארח הקוד.
המשפט שחשוב לזכור: החזקת מפתח צימוד אינה גבול הגישה; החברות היא הגבול.
מדוע לפצל כך
קטע בשם “מדוע לפצל כך”שלושה לחצים דחפו אל עבר שרת:
- מקור אמת אחד. שולחן עבודה וטלפון, או מחשב נייד ושולחן עבודה, אמורים לראות את אותו מצב צי. אילו כל אחד החזיק במסד נתונים משלו, הם היו מתפצלים. שרת יחיד משמעו שכל לקוח קורא את אותם נתונים חיים.
- הווב אינו יכול לשרת את עצמו. דפדפן אינו יכול להריץ תהליך בן או לפתוח קובץ מקומי, ולכן בניית ווב לעולם לא תוכל להחזיק במסד הנתונים או להריץ סוכן. היא חייבת לדבר עם שרת שכן עושה זאת. במקום לבנות נתיב נתונים שני שיועד לווב בלבד, הובא שולחן העבודה אל אותו נתיב.
- הפעלה ללא ממשק. שרת שאפשר להריץ על קופסה קטנה — נגיש מטלפון ברכבת או מדפדפן בכל מקום — שימושי יותר מממשק גרפי שצריך להשאיר פתוח.
הפשרה היא שהשרת הוא כעת תלות: אם הוא לא רץ, שום דבר לא עובד. תשובת ברירת המחדל של שולחן העבודה לכך היא להריץ שרת משל עצמו.
הלקוחות
קטע בשם “הלקוחות”חמישה לקוחות מגיעים אל השרת, ולכל אחד צורה ופרופיל אמון שונים.
אפליקציית שולחן העבודה
קטע בשם “אפליקציית שולחן העבודה”אפליקציית שולחן העבודה של Flutter (macOS, Windows, Linux) היא הלקוח המלא. בהפעלה הראשונה היא שואלת כיצד לרוץ, ואז זוכרת את הבחירה:
- מקומי: הרצה ופיקוח על
cc_serverבמחשב הזה — ברירת המחדל, מערכת עצמאית למשתמש יחיד. שולחן העבודה מפעיל את השרת, שמחזיק במסד הנתונים תחת שורש התמיכה של האפליקציה ומדבר איתו דרך הלולאה המקומית. - מרוחק: התחברות אל
cc_serverשרץ במקום אחר דרך WebSocket מאובטח. הנתונים חיים בשרת ההוא; שולחן העבודה הזה הוא מציג בלבד.
בשני המקרים לקוח ה-RPC שנוצר מחליף את ספק הנתונים של האפליקציה, כך שכל ממשק המשתמש — כל מסך, כל תכונה — קורא וכותב דרך השרת. שולחן העבודה לעולם אינו נוגע ישירות בקובץ מסד הנתונים.
לקוח הווב
קטע בשם “לקוח הווב”בניית הווב היא אותה אפליקציית Flutter, מהודרת לריצה בדפדפן, שמתארחת
ב-app.usectrl.dev כך שאין מה להתקין. דפדפן אינו
יכול להריץ תהליך בן, ולכן לקוח הווב הוא תמיד מרוחק: הוא מחייג אל
cc_server דרך wss:// ומרנדר את ממשק שולחן העבודה המלא. מרגע ההתחברות הוא
מציג את אותם מסכים מול אותו צי, כי זה אותו קוד מעל אותו RPC. הפערים הם קומץ
המקומות שזקוקים ליכולת מקומית שאין לדפדפן — שכבת האיכות של קליטת הפגישות
שלהלן היא העיקרית שבהם, והתראות מערכת ההפעלה הן אחרת.
זו הסיבה ש“בלי הורדה” ו“בלי שרת” הן טענות שונות. לקוח הווב מסיר את
ההתקנה; הוא אינו מסיר את השרת. מה שהוא מחייג אליו יכול להיות cc_server על
אותו מחשב נייד — ws://localhost:9030, שאינו צריך תעודה כי דפדפנים רואים
בלולאה המקומית משהו מהימן — או שרת על מכונה אחרת, שחייב להיות נגיש דרך
wss:// עם תעודה שהדפדפן סומך עליה. cc_server מסרב לאגד כתובת שאינה לולאה
מקומית בלי TLS, כך ששרת מרוחק משמעו תעודה משל עצמו, פרוקסי שמסיים TLS, או
מנהרה או VPN שמספקים תעודה כזו.
החבילה עצמה מתארחת בנפרד מהשרת שאיתו היא מדברת. cc_server עצמאי מגיש את
נקודת הקצה של RPC, את משטח הכלים של MCP ואת הפרוקסים שלו, אבל לא חבילת
ווב — נתיב הקבצים הסטטיים קיים בשרת, אך בבינארי העצמאי לא מוגדר שורש ווב,
ולכן אין לו מה להגיש בנתיב /. תמונת הקונטיינר המפורסמת cc-webapp היא
שמארחת את החבילה, ו-scripts/build_web.sh הוא שבונה אותה. שרת מקבל מקורות
דפדפן רק אם הם מופיעים ברשימת ה---allowed-origins שלו, שכברירת מחדל מכילה
את https://app.usectrl.dev, כך שאירוח עצמי של החבילה משמעו הוספה של המקור
שלה.
מדיה (תמונות פרופיל, תמונות פיד, צילומי מסך של בקשות משיכה) מנותבת דרך נקודת הקצה של הפרוקסי בשרת במקום לפנות ישירות אל מארחים במעלה הזרם, כך שדפדפן לעולם לא מביא מקור שרירותי. הלקוח הפרוס מפיק לכל בקשה כותרת Content-Security Policy מעוגייה לא רגישה, כך שרק המקור של השרת שאליו הוא מחובר מותר.
בן הלוויה בטלפון (cc_remote)
קטע בשם “בן הלוויה בטלפון (cc_remote)”אפליקציית Remote היא לקוח נפרד וקליל יותר — PWA של Flutter לווב ב-remote.usectrl.dev. היא במכוון לא האפליקציה המלאה. ההצמדות שלה נעשית דרך ממסר WebSocket מתווך וחתום מקצה לקצה, והיא מדברת תת-קבוצה שרובה קריאה של משטח הכלים: קריאה של כרטיסים, הודעות ופיד החדשות; שליחת תגובה או עדכון של כרטיס.
טלפון הוא ישות בעלת הרשאה נמוכה יותר מסוכן מקומי. מדיניות כלים שדוחה כברירת מחדל, מפתח מקדים משותף שחותם כל מסגרת, הרשאת הפעלה שחוסמת פעולות מורשות (למשל צימוד) בפני לקוח בן לוויה, בדיקות התפקיד וההרשאה ברמת המאגר לכל קריאה שתוארו לעיל והגבלת קצב — כל אלה מונעים מצימוד מאושר (או כזה שדלף) להפוך לשליטה מרחוק מלאה. ראו שליטה מרחוק ונייד למודל האבטחה המלא.
לקוחות MCP
קטע בשם “לקוחות MCP”כלים חיצוניים — עורכים, סוכנים אחרים, כלי שורת פקודה — שמדברים את Model Context Protocol מתחברים אל משטח הכלים של MCP בשרת וצורכים את מרשם הכלים בעל הטיפוסים. זהו נתיב האינטגרציה הפרוגרמטי; הוא אינו ממשק גרפי. ראו שימוש בשרת MCP ואת עזר כלי MCP.
עובדי צי (cc_worker)
קטע בשם “עובדי צי (cc_worker)”cc_worker הוא בינארי Dart טהור ללא ממשק שמצומד אל cc_server, מצהיר על
יכולות המארח שלו ומושך משימות מוחכרות לביצוע. הוא משדר אירועי תהליך בחזרה
דרך אותו RPC ואינו מחזיק מצב עמיד: בלי מסד נתונים, בלי אימות, בלי
אישורים, בלי תקציבים — אלה לעולם לא עוזבים את cc_server. עובד הוא איבר,
לא מוח שני. שרת סמכותי אחד, N איברים טיפשים; אין קונצנזוס ואין תעבורה בין
עובדים.
הרצה מוחכרת היא עדיין צנרת החכרה. agentRun הוא הסוג היחיד שמריץ פקודה
אמיתית (CC_JOB_COMMAND, או הנחיה שמוחזרת כהד אם לא סופקה הנחיה).
pipelineStep, codeIndex, goldenRender, benchmark ו-evalBatch מריצים
בינתיים בדיקה עם git --version / rev-parse HEAD, כך שנתיב הזרימה נבדק בלי
להעמיד פנים שהמשימות האלה רצו.
מה שהוא מפרסם הוא מערכת ההפעלה, ארכיטקטורת המעבד, מספר הליבות, הזיכרון, האם
Flutter SDK נגיש ואילו מנועי ארגז חול יש למארח (ב-Windows אין). עובד ייעודי
גם תמיד מפרסם alwaysOn ו-acceptsParallel, ואלה מה שהמתזמן
pin → prefer → spill משבץ לפיהם. אין בדיקה של GPU או ML.
שולחן עבודה בודד אינו מבחין בכל זה, כי שליחה רגילה אינה עוברת בצי כלל. עם
זאת, קיים עובד משתמע בתוך התהליך שרשום עבור מארח השרת, אבל זהו תפר רישום
בלי מריצים שמחוברים אליו — משימה שמונחת בפועל על העובד המקומי נכשלת במקום
לרוץ. כיום, רק cc_worker אמיתי מבצע משימות מוחכרות.
מה רץ איפה
קטע בשם “מה רץ איפה”| נושא | שרת (cc_server) |
עובד צי (cc_worker) |
לקוח שולחן עבודה | לקוח ווב / טלפון |
|---|---|---|---|---|
| מסדי נתונים (Drift/SQLite) | מחזיק בהם | — | — | — |
| אימות, אישורים, תקציבים, זהות | מחזיק בהם | — | — | — |
| פייפליינים, מנגנוני התאמת מצב, מאזין התזמור | מריץ אותם | — | — | — |
| משטח הכלים של MCP | מגיש אותו | — | — | — |
| ביצוע סוכנים | מריץ אותו (או מחכיר אותו החוצה) | מבצע משימות מוחכרות | — | — |
| רינדור ממשק המשתמש | — | — | מלא | מלא (ווב) / ברובו קריאה (טלפון) |
| קליטת פגישות (מיקרופון + אודיו הפגישה) | — | — | קליטה מקומית | ווב בלבד, דרך אודיו של שיתוף מסך |
| OAuth של לוח שנה (Google) | מאחסן אסימונים | — | — | — |
שתי השכבות אינן מפעילות ארגז חול באותו אופן. cc_server עוטף את הריצות
שהוא מבצע בעצמו בארגז החול של מערכת ההפעלה של המארח היכן שקיים מנוע — Seatbelt
ב-macOS, bubblewrap ב-Linux וב-WSL2 — ונופל בחזרה אל הגבולות האחרים ב-Windows
או ללא bwrap ו-socat. cc_worker אינו מפעיל ארגז חול כלל. הוא מפרסם
יכולת sandbox כדי שהשרת יוכל לשבץ משימות לפי פלטפורמה, אבל שום דבר בעובד
לא מיישם אותה: משימה מוחכרת מורצת ישירות, ומה שתוחם אותה הוא הסביבה של
ההחכרה ועץ העבודה שניתן לה. החכרת עבודה החוצה מעבירה אפוא את הביצוע אל מחוץ
לארגז החול, גם במארח שיש בו כזה. ראו
ארגז חול ואבטחה למה שתוחם ריצה
בכל מקרה.
קליטת פגישות מתרחשת בלקוח כי היא זקוקה למיקרופון ולאודיו של הפגישה — שרת בארון לא יכול להקליט את השיחה שלכם. ההקלטה מסוכמת לאחר מכן בפייפליין של השרת, וההערות נשמרות במקום שכל לקוח יכול לקרוא אותן.
גם לקוח הווב מקליט, מה שצורת הטבלה עלולה להסתיר: הוא קולט בדפדפן את המיקרופון ואת רצועת האודיו של שיתוף המסך ומשדר לשרת PCM בקצב 16 kHz. הוא דורש את רצועת האודיו הזו, ולכן נכשל במפורש במקום שבו הדפדפן לא יספק אותה. מה שבאמת ייחודי לשולחן העבודה הוא שכבת האיכות סביב הקליטה — ביטול הד ברמת האות דרך המעבד המקורי, מד עוצמת הקלט והתראה על מיקרופון מת, הרצה חוזרת של סיכום, ביטול עיבוד ופעולת ההקלטה והקישור של לוח השנה.
צימוד ואמון
קטע בשם “צימוד ואמון”כל לקוח מוכיח את זהותו באמצעות מפתח מקדים משותף שמוקצה פעם אחת:
- אפליקציית שולחן העבודה במצב מקומי מייצרת מפתח חדש בכל הפעלה ומוסרת אותו לשרת שהורץ, דרך הסביבה, כך ששום סוד לא נשמר בשולחן העבודה.
- לקוחות מרוחקים (שולחן עבודה מרוחק, ווב, טלפון) משתמשים במפתח שמונפק
בפקודת
pairשל השרת ונשמר במחזיק המפתחות של מערכת ההפעלה (או, בטלפון, מקומית).
ביטול הוא מיידי, לא נדחה. השרת עוקב אחר טבלת המכשירים המצומדים שלו ומנתק כל הפעלה פתוחה שהמכשיר שלה יצא מהקבוצה הפעילה, בתוך שניות — גם בנתיב ה-WebSocket הישיר וגם בנתיב הממסר. אין צורך להמתין לכך שהמכשיר יתחבר מחדש כדי לנתק אותו.
המפתח לעולם לא עובר בכתובת URL שמארח סטטי יכול לראות. כשהוא מגיע בקישור —
בטלפון או בדפדפן — הוא נמצא בקטע ה-URL, החלק שאחרי #, שאותו הדפדפן
לעולם לא שולח לשרת שמארח את הדף. זהו החוק שכדאי לזכור, והוא חל על שניהם.
ללקוח הווב יש שתי נקודות כניסה נוספות: טופס ההתחברות, שבו מדביקים כתובת שרת
ומפתח ישירות, וקוד הזמנה, שאותו הוא ממיר לאישור שנופק עבור הדפדפן הזה בלבד.
מושגים קשורים
קטע בשם “מושגים קשורים”- סביבות עבודה ובידוד: מדוע כל קריאת RPC נושאת את מזהה סביבת העבודה שלה וכיצד פיצול מסדי הנתונים הופך את הבידוד למבני
- ארכיטקטורה: כיצד בסיס הקוד בנוי סביב השרת והלקוחות שלו
- שליטה מרחוק ונייד: מודל הצימוד והאבטחה של הטלפון
מדריכים קשורים
קטע בשם “מדריכים קשורים”- הפעלת שרת ללא ממשק: הפעלת
cc_serverוצימוד לקוח - התחברות לשרת מרוחק: הפניית לקוח שולחן העבודה או הווב אל שרת במקום אחר
- שיוך מכשיר: הוספת טלפון, דפדפן שני או שולחן עבודה נוסף
- הפעלת worker בצי: הוספת
cc_workerשמושך משימות מוחכרות