גשר צ'אט
ספקים
קטע בשם “ספקים”| ספק | ערך תעבורה | תעבורה | סוג מרחב |
|---|---|---|---|
| Slack | slack |
Socket Mode (WSS יוצא) | slack |
Slack הוא הספק היחיד. ChatProvider הוא enum בעל ערך יחיד ו-cc_server רושם תוסף אחד. הסכימה, כל חיפוש וכל אינדקס ייחודי ממופתחים לפי provider, כך שמזהים לעולם לא חוצים ספקים והוספת ספק היא שינוי בצד השרת בלבד.
אפליקציה אחת בצד הספק לכל סביבת עבודה × ספק.
פקודות צ’אט
קטע בשם “פקודות צ’אט”שם הפקודה הוא לפי אפליקציה (cc כברירת מחדל, ולכן /cc). מנהל יכול לשנות את שמו; הגשר משתמש אז במה שאיתו הופעל לאחרונה.
| פקודה | השפעה |
|---|---|
/cc help (או /cc בלי פועל) |
מפרט מה הבוט יודע לעשות |
/cc link <code> |
מממש קוד חד־פעמי ומקשר את זהות הצ’אט של השולח |
/cc ticket <title> |
פותח כרטיס מקומי שמדווח על ידי החבר המקושר |
/cc ticket <title> | <description> |
אותו דבר, כשכל מה שאחרי ה-| הראשון הוא התיאור |
| פועל לא מוכר | מדווח על הפועל הלא מוכר יחד עם טקסט העזרה |
דרכים להגיע לסוכן:
| מחווה | התנהגות |
|---|---|
@bot … בערוץ |
פותח שרשור ב-Slack ומרחב Control Center תואם; הסוכן משיב בשרשור ה-Slack |
| תשובה בשרשור Slack מגושר | ממשיכה את אותו מרחב Control Center — בלי צורך באזכור |
| הודעה רגילה בערוץ | מתעלמים ממנה (לא מכוונת לבוט ולא בתוך שרשור Slack מגושר) |
| הודעה ישירה לבוט | מרחב Control Center אחד לכל שיחת הודעה ישירה; מגדיר כותרת לשרשור ה-Slack וסטטוס “is thinking…” בזמן שהסוכן עובד |
שדות פרטי גישה (Slack)
קטע בשם “שדות פרטי גישה (Slack)”מוצהרים על ידי מתאר הספק, שהוא זה שמייצר את דיאלוג החיבור ומאמת את השליחה.
| מזהה שדה | תווית | נדרש | קידומת | היכן הוא נמצא |
|---|---|---|---|---|
botToken |
אסימון גישה של הבוט | כן | xoxb- |
OAuth & Permissions |
appToken |
אסימון גישה ברמת האפליקציה | כן | xapp- |
Basic Information → App-Level Tokens, הרשאה connections:write |
configRefreshToken |
אסימון גישה לתצורת האפליקציה | לא | xoxe |
אסימוני תצורת אפליקציה ב-api.slack.com/apps |
configRefreshToken הוא פרט הגישה לניהול האפליקציה: בלעדיו החיבור עובד, אבל אי אפשר ליצור או לערוך את הבוט מ-Control Center. הוא מתחלף בכל שימוש והערך המתחלף נשמר מיד.
הערכים נחתכים מרווחים מיותרים, מפתחות שלא הוצהרו נזרקים, ואי־התאמה בקידומת נדחית באותה מחרוזת גם בלקוח וגם בשרת. שדות נדרשים חייבים להיות נוכחים בכל חיבור; שדה אופציונלי שנשאר ריק שומר על מה שכבר היה שמור.
דרישות אפליקציית Slack
קטע בשם “דרישות אפליקציית Slack”הרשאות אסימון הבוט:
app_mentions:readassistant:writechannels:historychat:writecommandsgroups:historyim:historyusers:readusers:read.emailמינויים לאירועי הבוט:
app_home_openedapp_mentionmessage.channelsmessage.groupsmessage.imהגדרות מבניות ש-Control Center כופה כשהוא יוצר או עורך את האפליקציה: Socket Mode מופעל, בלי כתובת URL של בקשה, לשונית Messages מופעלת וניתנת לכתיבה, always_online לבוט, ההרשאות והאירועים הנדרשים ממוזגים אל מה שכבר יש לאפליקציה (ולעולם לא נגרעים ממנו), ופקודת קו נטוי אחת שנושאת את התיאור File a ticket or link your Control Center account — הסימן שמזהה את הפקודה של Control Center עצמו ב-manifest מיוצא.
מצבי חיבור
קטע בשם “מצבי חיבור”| מצב | ערך תעבורה | משמעות |
|---|---|---|
| לא מחובר | disconnected |
לא נשמרו פרטי גישה עבור סביבת עבודה × ספק זו |
| מתחבר… | connecting |
פרטי הגישה נשמרו, התעבורה מחייגת או מחייגת מחדש |
| פעיל | connected |
התעבורה פעילה ומקבלת אירועים |
| שגיאת חיבור | error |
נשמרו אבל נכשלים; השגיאה של הספק עצמו מוצגת לצידם |
ההתחברות מחדש היא השהיה מעריכית עם jitter, שמוגבלת ל-30 שניות. סבב החלפת socket שיזם הספק מתחבר מחדש מיידית בלי להחמיר את ההשהיה. פרט גישה שנדחה (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) או הרשאה חסרה הם סוף הדרך: הגשר מפסיק לנסות שוב עד שמגיעים פרטי גישה חדשים.
יכולות הספק (Slack)
קטע בשם “יכולות הספק (Slack)”הגשר נכתב מול המשטח העשיר ביותר ומדרדר יכולת אחת בכל פעם.
| יכולת | Slack | השפעה כשהיא חסרה |
|---|---|---|
streaming |
כן | התשובה כולה מפורסמת פעם אחת, בסוף |
streamingRequiresThread |
כן | יעד תשובה בלי עוגן שרשור מפרסם תשובות שלמות |
ephemeralMessages |
כן | תשובה לפקודת קו נטוי בלי ידית תשובה שמישה נזרקת (סירובים תמיד מפורסמים בתוך השרשור) |
threadStatus |
כן | אין שורה חולפת של “is thinking…” |
threadTitle |
כן | שרשור הצ’אט שומר על כותרת ברירת המחדל שלו |
slashCommands |
כן | אין משטח /cc |
taskCards |
כן | טקסט בלבד — בלי כרטיס מעל התשובה ובלי קישור “View in Control Center” |
maxMessageLength |
39,000 | תשובות ארוכות יותר נחתכות עם הפניה ל-Control Center |
maxStreamChunkLength |
3,800 | תוספות מפוצלות בגבול של פסקה, שורה או רווח |
הצהרה על הזרמה אינה הבטחה להזרמה: זו של Slack דורשת את התכונה Agents & AI Apps בתוכנית בתשלום. סירוב בזמן הקריאה (streaming_not_supported, feature_not_enabled, paid_only, …) נזכר לכל חיי החיבור ומוצג כ-streamingAvailable: false. כרטיסי המשימה של Slack נוסעים על אותה קריאה, ולכן Slack שמסרב למצב התצוגה של הכרטיס שומר על הזרם שלו ומאבד רק את הכרטיס, פעם אחת, לכל חיי החיבור.
זרם גם צריך לנקוב בקורא שלו. Slack דורש גם משתמש נמען וגם את הצוות שלו כדי לפתוח אחד בערוץ (הודעה ישירה לא) ומסרב עם missing_recipient_user_id / missing_recipient_team_id אחרת — סירוב לפי קריאה, ולכן הגשר ממשיך לנסות ובינתיים התור מגיע כתשובה אחת מפורסמת. הנמען הוא מי ששאל, נלקח מההודעה הנכנסת (הצוות שלו, לא של האפליקציה, כך שערוץ Slack Connect נפתר נכון). לתור שאיש לא התחיל מהצ’אט — עבודה שהופעלה ב-Control Center על שיחה שכבר מגושרת — זה החבר שקישורו יצר את הגשר; אם הוא ביטל מאז את הקישור של חשבון הצ’אט שלו, אותו תור מפורסם שלם במקום להיות מוזרם.
כרטיסי משימה
קטע בשם “כרטיסי משימה”כרטיס מקובץ אחד לכל תור של סוכן (task_display_mode: plan), עם שורה לכל שלב. כל שורה היא task_update של id + title + status, ובנוסף details חד־פעמי כשהמחשבה מוכנה. details ו-output עם אותו מזהה משורשרים בלי מפריד, ולכן details נשלחים פעם אחת ו-output לעולם אינו מוזרם. הכותרת של התוכנית היא הבקשה; התשובה היא תוספת נפרדת ומאוחרת יותר שנושאת מקטע markdown_text.
| חלק | תוכן |
|---|---|
| כותרת התוכנית | מה שהתבקש |
| שורת הקמה | Working on it…, אחר כך Cloning …, אחר כך Starting the agent… — שורה אחת, הכותרת מוחלפת |
| שורת חשיבה | Thinking…, נוספת כשהסוכן מתחיל לחשוב; המחשבה היא ה-details של השורה הזו, נשלחת פעם אחת כשהחשיבה מסתיימת |
| שורות כלים | אחת לכל כלי (Bash which swift), מזהה ייחודי כך שהן יושבות זו לצד זו |
| סטטוס | בתהליך בזמן שהשורה הזו רצה; הושלם כשהשורה הבאה מתחילה או שהתור מסתיים; שגיאה במקרה של כשל, הפסקה או הגעה למגבלת התור |
| קריאה לפעולה | View in Control Center כשורה אחרונה כשהתור מסתיים — נעדרת כשהעבודה עדיין רצה ונעדרת כשהלשרת אין כתובת ציבורית נגישה |
מה שכרטיס לעולם אינו נושא: פלט כלים, ארגומנט שמחוץ לרשימת ההיתר הזו, או כל דבר שנקרא ממסד הנתונים. פרוזת החשיבה היא רק ה-details של שורת ה-Thinking… (מוגבלת ל-256 תווים), לא גוף ההודעה. כרטיס שלא יאמר דבר חדש אינו נשלח שוב. אזכור פותח את הזרם מיד כדי ששרשור ה-Slack לא יישאר שקט; תור שלא הפיק אחר כך שום טקסט מסיים את אותו כרטיס בהערה קצרה במקום לפרסם הודעה שנייה. תור בלי מילים במרחב שמעולם לא פתח זרם (אין כרטיסים, או שההזרמה כבר נדחתה) עדיין מפרסם הודעה אחת שנושאת את הכרטיס המוגמר ואת ההערה.
כרטיסים מוזרמים ומפורסמים הם מטענים שונים:
| צורה | מבנה | פרוזה |
|---|---|---|
| מוזרם | plan_update בתוספת מקטעי task_update שממופתחים לפי id |
מחרוזת פשוטה, מוגבלת ל-256 תווים לכל מקטע |
| מפורסם | בלוק plan שעוטף task_card-ים שממופתחים לפי task_id |
טקסט עשיר |
טיפול בכשלים:
| מצב | התנהגות |
|---|---|
| הספק מסרב לכרטיס בתוספת | התוספת נשלחת שוב מיד בלעדיו; הפרוזה עדיין מגיעה בזמן אמת והכרטיסים מושלכים לכל חיי החיבור |
| הספק מסרב גם לפרוזה | הזרם נסגר והתשובה מפורסמת שלמה |
| הזרם נשבר אחרי שחלק מהתשובה כבר נחת | הזרם נסגר ושום דבר לא מפורסם שוב |
ראו גשרי צ’אט למה המטען מפוצל כך.
הקמה, לפני שיש תור
קטע בשם “הקמה, לפני שיש תור”הכרטיס נפתח כשאזכור מתקבל, לפני השיבוט ולפני האסימון הראשון; התור שבא אחר כך מאמץ את אותו כרטיס וזרם. הקריינות שאחרי זה מגיעה מ-SpaceProvisioningChanged על אפיק אירועי התחום — אותה התקדמות שבאנר ההקמה של אפליקציית שולחן העבודה קורא משורת המרחב.
| מצב הקמה | קריינות |
|---|---|
| האזכור התקבל | Working on it… |
שלב repo |
Cloning <repo>… |
שלב prCheckout |
Checking out the pull request in <repo>… |
שלב script |
Running the setup script for <repo>… |
שלב agent |
Setting up <agent>… |
provisioning, עדיין בלי שלב |
Preparing the workspace… |
ready |
Starting the agent… |
| הסוכן חושב | Thinking… |
failed |
Workspace setup failed — details are in Control Center. |
cancelled |
Workspace setup was stopped — details are in Control Center. |
קריינות ההקמה דורשת את כל ארבעת התנאים:
| תנאי |
|---|
| הספק מציג כרטיסים |
| הזרמה זמינה בחיבור הזה |
| במרחב יש בקשת צ’אט בדרך |
| שום כרטיס לא מדווח כבר על תור שם |
הבקשה נשארת בדרך עד שתור מתחיל או שההקמה נכשלת. אירוע ready בלי כרטיס שכבר פתוח הוא מרחב חם ונשאר בשקט, והקמה שאיש בצ’אט לא ממתין לה — שיחה שנפתחה באפליקציה, או תקועה שהתחדשה באתחול — אינה מייצרת כרטיס כלל.
עמוד ההפניה של /open
קטע בשם “עמוד ההפניה של /open”מוצרי צ’אט מקבלים http(s) בקישור ושום דבר אחר, ולכן קריאה לפעולה לא יכולה לנקוב ישירות בסכימה של אפליקציית שולחן העבודה.
| מאפיין | ערך |
|---|---|
| נתיב | GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id> |
| מקור | נגזר מכתובת ה-URL הציבורית של השרת (ws → http, wss → https); loopback נשמר |
| תגובה | עמוד HTML קטן עם no-store שמנווט אל control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> ומציג כפתור ידני |
| אימות | מזהים חייבים להתאים ל-[A-Za-z0-9_.-]{1,128}; כל דבר אחר הוא 404 |
| גישה | ציבורי ואינרטי — הוא לא קורא שום מסד נתונים, ולכן אינו אורקל קיום; האפליקציה שהוא פותח מבצעת את האישור |
שדות פרופיל הבוט
קטע בשם “שדות פרופיל הבוט”| שדה | מגבלה | הערות |
|---|---|---|
appName |
1–35 תווים | ברירת מחדל: Control Center · <workspace name> |
botDisplayName |
1–80 תווים | ברירת מחדל: control-center |
description |
≤ 140 תווים | |
agentDescription |
≤ 300 תווים | מוצג מעל הודעה ישירה עם הבוט |
command |
≤ 32 תווים | מנורמל: קווים נטויים מובילים מוסרים, אותיות מוקטנות, [^a-z0-9_-] מוסר, ריק נופל חזרה ל-cc |
agentEnabled |
— | האם חוויית ההודעה הישירה/סוכן חשופה |
תיבה ריקה פירושה “השאר אותו כמו שהוא”, לא “נקה אותו”. המגבלות הן של Slack, שהן ההדוקות מבין המוצרים שמופו.
קודי קישור
קטע בשם “קודי קישור”| מאפיין | ערך |
|---|---|
| אורך | 6 תווים |
| אלפבית | 23456789ABCDEFGHJKLMNPQRSTVWXYZ (בלי 0/O, 1/I, U) |
| תוקף | 15 דקות |
| שימושים | אחד |
| אחסון | בזיכרון — הפעלה מחדש של השרת מבטלת קודים פתוחים |
| היקף | סביבת עבודה אחת וספק אחד; הנפקת קוד חדש מבטלת את הקוד הקודם של החבר |
המימוש מותאם בלי תלות ברישיות. קודים לא מוכרים, שפג תוקפם, של סביבת עבודה זרה ושל ספק שגוי נכשלים כולם באופן זהה בכוונה.
שיטות קישור
קטע בשם “שיטות קישור”| שיטה | ערך תעבורה | איך |
|---|---|---|
| קוד | code |
החבר מימש /cc link <code> |
| אימייל | email |
האימייל המאומת של הספק התאים למשתמש שהוא כבר חבר בסביבת העבודה |
פעולות RPC
קטע בשם “פעולות RPC”כולן בהיקף סביבת עבודה. השרת חסר מצב, ולכן כל קריאה נושאת את ה-workspace_id שלה ב-args; המשגר מסרב לפעולה בהיקף סביבת עבודה שמשמיטה אותו, לפני שמסד נתונים כלשהו נפתח. פרטי גישה נוסעים פנימה בלבד — קריאות סטטוס לעולם אינן מחזירות אותם.
| פעולה | סוג | תפקיד מזערי | מחלקות פעולה |
|---|---|---|---|
chat.providers |
קריאה | guest |
— |
chat.status |
קריאה | guest |
— |
chat.connect |
שינוי | admin |
secretAccess, networkEgress |
chat.disconnect |
הרסנית | admin |
secretAccess |
chat.listUserLinks |
קריאה | guest |
— |
chat.beginUserLink |
שינוי | member |
— |
chat.unlinkUser |
שינוי | member עבור עצמכם, admin עבור כל אחד אחר |
— |
chat.createApp |
שינוי | admin |
secretAccess, networkEgress |
chat.setupLink |
קריאה | admin |
— |
chat.botProfile |
קריאה | admin |
networkEgress |
chat.updateBotProfile |
שינוי | admin |
networkEgress |
פעולות קריאה יורשות את רצפת ה-guest מהסוג שלהן; פעולות שינוי בלי רצפה מפורשת דורשות member.
chat.setupLink מרכיב כתובת URL ליצירת אפליקציית ספק מתוך פרופיל הבוט. הוא לא מצהיר על מחלקת פעולה כי הוא לא קורא שום סוד ולא מחייג לשום מקום — הדפדפן של המשתמש עצמו מבצע את הקריאה היחידה.
שאילתה ריאקטיבית אחת משלימה את המשטח:
| שאילתה | סוג | פולטת |
|---|---|---|
chat.watchUserLinks |
מעקב | הרשימה, נדחפת מחדש בכל פעם שקישור נוצר או מוסר |
היא קיימת כי הקישור קורה ב_צד האחר_: חבר מקליד את הקוד החד־פעמי אל תוך הבוט, ולכן שום תגובת לקוח לא יכולה לשאת את התשובה בחזרה. רשימת ההגדרות והדיאלוג הפתוח “קישור החשבון שלי” עוקבים אחריה, והדיאלוג עובר לאישור ברגע שהקישור של החבר עצמו נוחת.
chat.beginUserLink תמיד מנפיק קוד עבור הזהות של הקורא עצמו. chat.updateBotProfile מחזיר remaining_step רק כשנשאר שלב (בדרך כלל התקנה מחדש); היעדרו פירושו שהעריכה חיה.
chat.providers מחזיר רשומה אחת לכל ספק מוצע — המתאר ובנוסף, כשיש, סטטוס החיבור. כל מה שממשק ההגדרות מציג מגיע מהמטען הזה, ולכן הוספת ספק היא שינוי בצד השרת בלבד.
אחסון
קטע בשם “אחסון”| מה | איפה |
|---|---|
| פרטי גישה של חיבור | <dataDir>/<workspaceId>/chat_credentials/<provider>.json |
| הקמה מודרכת שנותרה באמצע | <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json |
| קובצי Slack מדורות קודמים | <dataDir>/<workspaceId>/slack_credentials.json ו-slack_app_setup.json, נקראים פעם אחת ומומרים במקום |
פרטי הגישה יושבים לצד מסד הנתונים של סביבת העבודה, לא בתוכו: מחיקת סביבת עבודה מסירה איתה את אסימוני הצ’אט שלה, ו-workspace.export או גיבוי מעתיקים את workspace.db — כך שסביבת עבודה שנמסרת למישהו אחר אינה נושאת אסימוני בוט חיים. הכתיבות אטומיות (קובץ זמני ובנוסף שינוי שם) ומוקשחות להרשאת בעלים בלבד.
טבלאות מסד הנתונים
קטע בשם “טבלאות מסד הנתונים”שתיהן יושבות במסד הנתונים של סביבת העבודה, שנושא בסיס v1 מכווץ — ולכן קריאה חוצת סביבות עבודה לא מתקמפלת.
chat_space_links — שיחה חיצונית אחת ↔ מרחב Control Center אחד:
| עמודה | הערות |
|---|---|
id |
UUID v4 |
workspace_id |
היקף סביבת עבודה |
provider |
ChatProvider.wire |
external_team_id |
צוות Slack T…, ריק עבור ספקים בלי המושג |
external_channel_id |
ערוץ Slack C…, קבוצה G…, הודעה ישירה D… |
external_thread_id |
עוגן השרשור ב-Slack, או null כשכל השיחה היא העוגן (הודעות ישירות לבוט) |
cc_space_id |
REFERENCES spaces(id) ON DELETE CASCADE |
created_by_user_id, created_at, last_activity_at |
ייחודי על (workspace_id, provider, external_channel_id, external_thread_id), עם אינדקס חלקי שאוכף את אותו דבר עבור מזהי שרשור null (SQLite מתייחס ל-NULLs כנפרדים).
chat_user_links — חבר חיצוני אחד ↔ משתמש Control Center אחד:
| עמודה | הערות |
|---|---|
id |
UUID v4 |
workspace_id |
היקף סביבת עבודה |
provider |
ChatProvider.wire |
external_team_id, external_user_id |
חבר Slack U… |
user_id |
משתמש Control Center (גלובלי, ולכן בלי מפתח זר) |
method |
code או email |
linked_at |
ייחודי על (workspace_id, provider, external_team_id, external_user_id) ועל (workspace_id, provider, user_id) — ייחוס חד־ערכי בשני הכיוונים, בעוד שאדם אחד יכול להיות מקושר בכמה ספקים.
קישור הוא פענוח זהות, לא הענקת גישה: החברות נבדקת מחדש בכל אירוע נכנס, כך שקישור לחבר שהוסר מאז מפוענח כסירוב.
סוג המרחב ומטא־נתוני ההודעה
קטע בשם “סוג המרחב ומטא־נתוני ההודעה”מרחב מגושר נוצר עם ה-SpaceKind של הספק (slack). הוא מתנהג כמו מרחב רגיל שפונה לאנשים — מתריע ומסווג כמו topic — ו-isExternalChat הוא הבדיקה שכל התנהגות (שיקוף, תג, שימור) עוברת דרכה.
כל הודעה שהגשר מכניס נושאת חתימה של metadata['chat']:
{ "chat": { "provider": "slack", "teamId": "T024BE7LD", "channelId": "C0G9QF9GW", "threadId": "1700000000.000100", "messageId": "1700000000.000200", "userId": "U024BE7LH", "userLabel": "sam" }}החתימה היא גם שמירת ההד: השיקוף היוצא מדלג על כל הודעה שנושאת אותה, וזה מה שמונע מהודעת צ’אט נכנסת להתפרסם מיד בחזרה.
התנהגות הממסר
קטע בשם “התנהגות הממסר”| התנהגות | פירוט |
|---|---|
| מה מועבר החוצה | הטקסט של הסוכן בלבד, בתוספת שורת הסטטוס של כרטיס המשימה. פרוזת החשיבה ופלט הכלים נשארים ב-Control Center |
| קצב ההזרמה | מווסת לשטיפה אחת בשנייה בזמן שהתור רץ; הכרטיס והטקסט שהוא מסביר נוסעים באותה קריאה |
| הודעות של אנשים שהוקלדו ב-Control Center | משוקפות אל השרשור המקושר, עם שם התצוגה של הכותב לפני כן |
| תורים ריקים | מדווחים כהערה קצרה (ההרצה נכשלה, הופסקה או נעצרה במגבלת התורים שלה) ולא כשתיקה |
| הסרת כפילויות | ממופתח לפי מזהה האירוע של הספק עצמו; 512 האחרונים נזכרים |
| סוכן החזית | שיחה מגושרת חדשה מעירה את סוכן ה-CEO של סביבת העבודה, ואם אין — את הסוכן הראשון שלה, ואם אין — איש |
| שמות מרחבים | #channel · <request> עבור שרשור בערוץ Slack, Slack DM · <request> עבור הודעה ישירה, כותרות מוגבלות ל-48 תווים |
| בידוד כשלים | אירוע רע אחד לעולם לא מפיל את הגשר; החיבור ממשיך לשרת כל שיחה אחרת |
ראו גם
קטע בשם “ראו גם”- הגדרת אינטגרציית Slack
- קישור חשבון Slack
- התאמת בוט הצ’אט
- גשרי צ’אט
- מעקות בטיחות עבור מחלקות הפעולה שפעולות ה-RPC מצהירות עליהן