ข้ามไปยังเนื้อหา

สะพานแชท

ผู้ให้บริการ ค่าไวร์ ทรานสปอร์ต ชนิดสเปซ
Slack slack Socket Mode (WSS ขาออก) slack

Slack เป็นผู้ให้บริการรายเดียว ChatProvider เป็น enum ที่มีค่าเดียว และ cc_server ลงทะเบียนปลั๊กอินหนึ่งตัว สคีมา การค้นหาทุกครั้ง และดัชนีไม่ซ้ำทุกตัวใช้ provider เป็นคีย์ ID จึงไม่ข้ามผู้ให้บริการกัน และการเพิ่มผู้ให้บริการอีกรายเป็นการเปลี่ยนแปลงฝั่งเซิร์ฟเวอร์เท่านั้น

หนึ่งแอปฝั่งผู้ให้บริการต่อ เวิร์กสเปซ × ผู้ให้บริการ

ชื่อคำสั่งเป็นรายแอป (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…” ขณะเอเจนต์ทำงาน

ประกาศโดย descriptor ของผู้ให้บริการ ซึ่งเป็นสิ่งที่สร้างกล่องโต้ตอบการเชื่อมต่อและตรวจสอบข้อมูลที่ส่งมา

ID ฟิลด์ ป้ายกำกับ จำเป็น คำนำหน้า อยู่ที่ไหน
botToken โทเค็น OAuth ของผู้ใช้บอท ใช่ xoxb- OAuth & Permissions
appToken โทเค็นระดับแอป ใช่ xapp- Basic Information → App-Level Tokens, สโคป connections:write
configRefreshToken โทเค็นรีเฟรชการตั้งค่าแอป ไม่ xoxe App configuration tokens บน api.slack.com/apps

configRefreshToken คือข้อมูลรับรองสำหรับจัดการแอป: หากไม่มี การเชื่อมต่อยังทำงานได้ แต่บอทจะสร้างหรือแก้ไขจาก Control Center ไม่ได้ มันหมุนเวียนใหม่ทุกครั้งที่ใช้ และค่าที่หมุนใหม่จะถูกจัดเก็บทันที

ค่าจะถูกตัดช่องว่างหัวท้าย คีย์ที่ไม่ได้ประกาศไว้จะถูกทิ้ง และคำนำหน้าที่ไม่ตรงจะถูกปฏิเสธด้วยข้อความเดียวกันทั้งฝั่งไคลเอนต์และเซิร์ฟเวอร์ ฟิลด์ที่จำเป็นต้องมีทุกครั้งที่เชื่อมต่อ ส่วนฟิลด์ที่ไม่บังคับซึ่งเว้นว่างไว้จะคงค่าที่จัดเก็บไว้เดิม

สโคปของโทเค็นบอท:

app_mentions:read
assistant:write
channels:history
chat:write
commands
groups:history
im:history
users:read
users:read.email

การสมัครรับเหตุการณ์ของบอท:

app_home_opened
app_mention
message.channels
message.groups
message.im

การตั้งค่าเชิงโครงสร้างที่ Control Center กำหนดเมื่อสร้างหรือแก้ไขแอป: เปิด Socket Mode, ไม่มี request URL, เปิดแท็บ Messages และเขียนได้, บอท always_online, รวมสโคปและเหตุการณ์ที่จำเป็นเข้ากับสิ่งที่แอปมีอยู่แล้ว (ไม่เคยตัดออก) และคำสั่งสแลชหนึ่งคำสั่งที่ใช้คำอธิบาย File a ticket or link your Control Center account — เครื่องหมายที่ระบุคำสั่งของ Control Center เองในแมนิเฟสต์ที่นำออก

สถานะ ค่าไวร์ ความหมาย
ยังไม่เชื่อมต่อ disconnected ยังไม่มีข้อมูลรับรองที่จัดเก็บไว้สำหรับเวิร์กสเปซ × ผู้ให้บริการนี้
กำลังเชื่อมต่อ… connecting จัดเก็บข้อมูลรับรองแล้ว ทรานสปอร์ตกำลังเชื่อมต่อหรือเชื่อมต่อใหม่
ออนไลน์ connected ทรานสปอร์ตทำงานอยู่และกำลังรับเหตุการณ์
ข้อผิดพลาดในการเชื่อมต่อ error จัดเก็บไว้แต่ล้มเหลว; ข้อผิดพลาดของผู้ให้บริการเองจะแสดงควบคู่กัน

การเชื่อมต่อใหม่ใช้การถอยกลับแบบเอกซ์โพเนนเชียลพร้อม jitter และจำกัดไว้ที่ 30 วินาที การหมุนซ็อกเก็ตที่ผู้ให้บริการเป็นผู้เริ่มจะเชื่อมต่อใหม่ทันทีโดยไม่เพิ่มการถอยกลับ ข้อมูลรับรองที่ถูกปฏิเสธ (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) หรือสโคปที่หายไปถือเป็นจุดสิ้นสุด: สะพานจะหยุดลองใหม่จนกว่าจะมีข้อมูลรับรองใหม่มา

สะพานถูกเขียนขึ้นโดยอิงพื้นผิวที่สมบูรณ์ที่สุด และจะลดความสามารถลงทีละรายการ

ความสามารถ 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 ที่มี id เดียวกันจะถูกต่อกันโดยไม่มีตัวคั่น details จึงถูกส่งครั้งเดียว และ output จะไม่ถูกสตรีมเลย ชื่อเรื่องของแผนคือคำขอ; คำตอบคือการต่อท้ายแยกต่างหากในภายหลังที่พา chunk markdown_text มา

ส่วน เนื้อหา
ชื่อเรื่องของแผน สิ่งที่ถูกถาม
แถวการเตรียมการ Working on it…, แล้ว Cloning …, แล้ว Starting the agent… — หนึ่งแถว โดยชื่อถูกแทนที่
แถวความคิด Thinking…, เพิ่มเมื่อเอเจนต์เริ่มให้เหตุผล; ความคิดคือ details ของแถวนี้ ส่งครั้งเดียวเมื่อการให้เหตุผลเสร็จสิ้น
แถวเครื่องมือ หนึ่งแถวต่อเครื่องมือหนึ่งรายการ (Bash which swift) ใช้ id ไม่ซ้ำเพื่อให้วางเคียงกัน
สถานะ กำลังดำเนินการขณะแถวนั้นทำงาน; เสร็จสมบูรณ์เมื่อแถวถัดไปเริ่มหรือเทิร์นจบ; ผิดพลาดเมื่อล้มเหลว ถูกขัดจังหวะ หรือถึงขีดจำกัดเทิร์น
การเรียกให้ลงมือ View in Control Center เป็นแถวท้ายเมื่อเทิร์นจบ — ไม่มีขณะงานยังทำงานอยู่ และไม่มีเมื่อเซิร์ฟเวอร์ไม่มี URL สาธารณะที่เข้าถึงได้

สิ่งที่การ์ดไม่เคยพามา: เอาต์พุตของเครื่องมือ, อาร์กิวเมนต์ที่อยู่นอกรายการที่อนุญาตนั้น, หรือสิ่งใดก็ตามที่อ่านจากฐานข้อมูล ข้อความบรรยายการให้เหตุผลมีเพียง details ของแถว Thinking… (จำกัด 256 ตัวอักษร) ไม่ใช่เนื้อความของข้อความ การ์ดที่จะไม่บอกอะไรใหม่จะไม่ถูกส่งซ้ำ การกล่าวถึงจะเปิดสตรีมทันทีเพื่อให้เธรด Slack ไม่เงียบ; เทิร์นที่ต่อมาไม่ผลิตข้อความใดเลยจะปิดการ์ดใบเดิมนั้นด้วยบันทึกสั้น ๆ แทนที่จะโพสต์ข้อความที่สอง เทิร์นที่ไม่มีข้อความบนสเปซที่ไม่เคยเปิดสตรีม (ไม่มีการ์ด หรือสตรีมถูกปฏิเสธไปแล้ว) จะยังโพสต์ข้อความหนึ่งฉบับที่พาการ์ดที่เสร็จแล้วและบันทึกนั้นมา

การ์ดที่สตรีมกับการ์ดที่โพสต์เป็นเพย์โหลดที่ต่างกัน:

รูปแบบ โครงสร้าง ข้อความบรรยาย
สตรีม plan_update บวก chunk task_update ที่ใช้ id เป็นคีย์ สตริงธรรมดา จำกัด 256 ตัวอักษรต่อ chunk
โพสต์ บล็อก plan ที่ห่อ task_card ซึ่งใช้ task_id เป็นคีย์ ข้อความแบบ rich text

การจัดการความล้มเหลว:

สถานการณ์ พฤติกรรม
ผู้ให้บริการปฏิเสธการ์ดในการต่อท้ายหนึ่งครั้ง การต่อท้ายจะถูกลองใหม่ทันทีโดยไม่มีการ์ด; ข้อความบรรยายยังมาถึงแบบสด และการ์ดจะถูกทิ้งไปตลอดอายุการเชื่อมต่อ
ผู้ให้บริการปฏิเสธข้อความบรรยายด้วย สตรีมถูกปิดและคำตอบถูกโพสต์ทั้งหมด
สตรีมขาดหลังจากคำตอบส่วนหนึ่งไปถึงแล้ว สตรีมถูกปิดและไม่มีอะไรถูกโพสต์ซ้ำ

ดู สะพานแชท ว่าทำไมเพย์โหลดจึงถูกแบ่งแบบนี้

การ์ดจะเปิดเมื่อการกล่าวถึงได้รับการยอมรับ ก่อนการโคลนและก่อนโทเค็นแรก; เทิร์นที่ตามมาจะใช้การ์ดและสตรีมเดียวกัน คำบรรยายหลังจากนั้นมาจาก 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 ที่ยังไม่มีการ์ดเปิดอยู่หมายถึงสเปซที่อุ่นอยู่แล้ว จึงถูกปล่อยให้เงียบ และการเตรียมการที่ไม่มีใครในแชทรอ — บทสนทนาที่เปิดในแอป หรือบทสนทนาที่ค้างอยู่และกลับมาทำงานต่อตอนบูต — ไม่ผลิตการ์ดใดเลย

ผลิตภัณฑ์แชทยอมรับเฉพาะ http(s) ในลิงก์และไม่รับอย่างอื่น การเรียกให้ลงมือจึงระบุสคีมาของเดสก์ท็อปโดยตรงไม่ได้

คุณสมบัติ ค่า
เส้นทาง GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id>
ต้นทาง ได้มาจาก URL สาธารณะของเซิร์ฟเวอร์ (ws → http, wss → https); ที่อยู่ลูปแบ็กยังคงไว้
การตอบกลับ หน้า HTML ขนาดเล็กที่ไม่แคช (no-store) ซึ่งนำทางไปยัง control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> และแสดงปุ่มให้กดเอง
การตรวจสอบ 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 อีเมลที่ผ่านการยืนยันของผู้ให้บริการตรงกับผู้ใช้ที่เป็นสมาชิกของเวิร์กสเปซอยู่แล้ว

ทั้งหมดจำกัดขอบเขตที่เวิร์กสเปซ เซิร์ฟเวอร์ไม่มีสถานะ คำเรียกแต่ละครั้งจึงพา workspace_id ของตัวเองมาใน args; dispatcher จะปฏิเสธ op ที่จำกัดขอบเขตเวิร์กสเปซซึ่งละเว้นค่านั้น ก่อนที่ฐานข้อมูลใดจะถูกเปิด ข้อมูลรับรองเดินทางเข้าเท่านั้น — การอ่านสถานะไม่เคยส่งคืนมันกลับมา

การดำเนินการ ชนิด บทบาทขั้นต่ำ คลาสการกระทำ
chat.providers read guest —
chat.status read guest —
chat.connect mutate admin secretAccess, networkEgress
chat.disconnect destructive admin secretAccess
chat.listUserLinks read guest —
chat.beginUserLink mutate member —
chat.unlinkUser mutate member สำหรับตัวเอง, admin สำหรับคนอื่น —
chat.createApp mutate admin secretAccess, networkEgress
chat.setupLink read admin —
chat.botProfile read admin networkEgress
chat.updateBotProfile mutate admin networkEgress

การดำเนินการอ่านสืบทอดระดับขั้นต่ำ guest จากชนิดของมัน; การดำเนินการแบบ mutate ที่ไม่มีระดับขั้นต่ำระบุชัดเจนต้องใช้ member

chat.setupLink ประกอบ URL สำหรับสร้างแอปของผู้ให้บริการขึ้นจากโปรไฟล์บอท มันไม่ประกาศคลาสการกระทำใดเพราะไม่อ่านความลับใดและไม่ต่อออกไปไหน — เบราว์เซอร์ของผู้ใช้เองเป็นผู้เรียกเพียงครั้งเดียว

มีคำสั่งสืบค้นเชิงตอบสนองหนึ่งรายการที่ทำให้พื้นผิวสมบูรณ์:

คำสั่งสืบค้น ชนิด ส่งออก
chat.watchUserLinks watch รายชื่อ ซึ่งถูกส่งใหม่ทุกครั้งที่มีการสร้างหรือลบลิงก์

มันมีอยู่เพราะการลิงก์เกิดขึ้นที่อีกฝั่ง: สมาชิกพิมพ์รหัสใช้ครั้งเดียวลงในบอท คำตอบจากไคลเอนต์จึงพาคำตอบกลับมาไม่ได้ รายชื่อในหน้าตั้งค่าและกล่องโต้ตอบ “ลิงก์บัญชีของฉัน” ที่เปิดค้างอยู่ต่างติดตามมัน และกล่องโต้ตอบจะเปลี่ยนไปเป็นการยืนยันทันทีที่ลิงก์ของสมาชิกเองมาถึง

chat.beginUserLink สร้างรหัสสำหรับตัวตนของตัวผู้เรียกเองเสมอ chat.updateBotProfile จะคืนค่า remaining_step เฉพาะเมื่อยังมีขั้นตอนเหลืออยู่ (โดยทั่วไปคือการติดตั้งใหม่); การไม่มีค่านั้นหมายความว่าการแก้ไขมีผลแล้ว

chat.providers คืนหนึ่งรายการต่อผู้ให้บริการที่เสนอ — descriptor บวกสถานะการเชื่อมต่อ (ถ้ามี) ทุกอย่างที่ UI ตั้งค่าแสดงล้วนมาจากเพย์โหลดนั้น จึงเป็นเหตุผลว่าทำไมการเพิ่มผู้ให้บริการจึงเป็นการเปลี่ยนแปลงฝั่งเซิร์ฟเวอร์เท่านั้น

อะไร ที่ไหน
ข้อมูลรับรองการเชื่อมต่อ <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 — เวิร์กสเปซที่ส่งต่อให้คนอื่นจึงไม่มีโทเค็นบอทที่ใช้งานได้ติดไป การเขียนเป็นแบบอะตอมมิก (ไฟล์ชั่วคราวบวกการเปลี่ยนชื่อ) และถูกจำกัดสิทธิ์ให้เฉพาะเจ้าของ

ทั้งสองอยู่ในฐานข้อมูลของเวิร์กสเปซ ซึ่งมี baseline 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) โดยมีดัชนีบางส่วนบังคับเช่นเดียวกันสำหรับ id ของเธรดที่เป็น null (SQLite ถือว่า NULL ต่างกัน)

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 (ทั่วโลก จึงไม่มี foreign key)
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 ถูกมิเรอร์ไปยังเธรดที่ลิงก์ไว้ โดยนำหน้าด้วยชื่อที่แสดงของผู้เขียน
เทิร์นว่าง ถูกรายงานเป็นบันทึกสั้น ๆ (การรันล้มเหลว ถูกขัดจังหวะ หรือหยุดที่ขีดจำกัดเทิร์นของมัน) แทนความเงียบ
การขจัดข้อมูลซ้ำ ใช้ id เหตุการณ์ของผู้ให้บริการเองเป็นคีย์; จำ 512 รายการล่าสุดไว้
เอเจนต์ประตูหน้า บทสนทนาที่เชื่อมสะพานใหม่จะปลุกเอเจนต์ CEO ของเวิร์กสเปซ มิฉะนั้นเอเจนต์ตัวแรก มิฉะนั้นไม่มีใคร
การตั้งชื่อสเปซ #channel · <request> สำหรับเธรดในแชนเนล Slack, Slack DM · <request> สำหรับข้อความส่วนตัว, ชื่อจำกัดที่ 48 ตัวอักษร
การแยกความล้มเหลว เหตุการณ์เสียหนึ่งครั้งไม่เคยทำให้สะพานล่ม; การเชื่อมต่อยังให้บริการบทสนทนาอื่นทุกอันต่อไป