Bỏ qua để đến nội dung

Cầu chat

Nhà cung cấp Giá trị truyền Transport Loại space
Slack slack Socket Mode (WSS hướng ra) slack

Slack là nhà cung cấp duy nhất. ChatProvider là một enum đơn trị và cc_server đăng ký một plugin. Schema, mọi phép tra cứu và mọi chỉ mục duy nhất đều được khóa theo provider, nên id không bao giờ vượt qua ranh giới nhà cung cấp và việc thêm một nhà cung cấp chỉ là một thay đổi phía máy chủ.

Một ứng dụng phía nhà cung cấp cho mỗi không gian làm việc × nhà cung cấp.

Tên lệnh là theo từng ứng dụng (cc theo mặc định, nên là /cc). Quản trị viên có thể đổi tên; khi đó cầu nối dùng bất kỳ tên nào mà nó được gọi lần gần nhất.

Lệnh Tác dụng
/cc help (hoặc /cc không có động từ) Liệt kê những gì bot có thể làm
/cc link <code> Đổi một mã dùng một lần và liên kết danh tính chat của người gửi
/cc ticket <title> Tạo một phiếu cục bộ do thành viên đã liên kết báo cáo
/cc ticket <title> | <description> Tương tự, với mọi thứ sau dấu | đầu tiên là phần mô tả
Động từ không xác định Báo động từ không xác định kèm văn bản trợ giúp

Những cách để tiếp cận một agent:

Thao tác Hành vi
@bot … trong một kênh Mở một luồng Slack và một space Control Center tương ứng; agent trả lời trong luồng Slack
Trả lời trong một luồng Slack đã nối Tiếp tục đúng space Control Center đó — không cần nhắc tên
Tin nhắn thường trong một kênh Bị bỏ qua (không gửi cho bot và không nằm trong một luồng Slack đã nối)
Tin nhắn riêng cho bot Một space Control Center cho mỗi cuộc hội thoại riêng; đặt tiêu đề luồng Slack và trạng thái “is thinking…” trong khi agent làm việc

Được khai báo bởi descriptor của nhà cung cấp — chính nó sinh ra hộp thoại kết nối và kiểm tra dữ liệu gửi lên.

Id trường Nhãn Bắt buộc Tiền tố Vị trí
botToken Token bot Có xoxb- OAuth & Permissions
appToken Token cấp ứng dụng Có xapp- Basic Information → App-Level Tokens, scope connections:write
configRefreshToken Token cấu hình app Không xoxe App configuration tokens trên api.slack.com/apps

configRefreshToken là thông tin xác thực quản lý ứng dụng: thiếu nó thì kết nối vẫn hoạt động, nhưng không thể tạo hay chỉnh sửa bot từ Control Center. Nó xoay vòng sau mỗi lần dùng và giá trị sau khi xoay được lưu ngay lập tức.

Các giá trị được cắt khoảng trắng ở hai đầu, những khóa không được khai báo bị loại bỏ và việc sai tiền tố bị từ chối bằng cùng một câu ở cả máy khách lẫn máy chủ. Các trường bắt buộc phải có mặt trong mọi lần kết nối; một trường tùy chọn để trống sẽ giữ nguyên giá trị đã được lưu trước đó.

Các scope của token bot:

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

Các đăng ký sự kiện của bot:

app_home_opened
app_mention
message.channels
message.groups
message.im

Các cài đặt cấu trúc mà Control Center khẳng định khi tạo hoặc chỉnh sửa ứng dụng: bật Socket Mode, không có request URL, tab Messages được bật và cho phép ghi, bot always_online, các scope và sự kiện bắt buộc được hợp nhất vào những gì ứng dụng đã có (không bao giờ bị trừ đi) và một lệnh slash mang mô tả File a ticket or link your Control Center account — dấu hiệu nhận ra lệnh của chính Control Center trong một manifest đã xuất.

Trạng thái Giá trị truyền Ý nghĩa
Chưa kết nối disconnected Không có thông tin xác thực nào được lưu cho không gian làm việc × nhà cung cấp này
Đang kết nối… connecting Đã lưu thông tin xác thực, transport đang quay số hoặc quay số lại
Đang hoạt động connected Transport đã lên và đang nhận sự kiện
Lỗi kết nối error Đã lưu nhưng đang lỗi; lỗi của chính nhà cung cấp được hiển thị kèm theo

Việc kết nối lại dùng backoff lũy thừa kèm jitter, giới hạn ở 30 giây. Một lần xoay socket do nhà cung cấp khởi tạo sẽ kết nối lại ngay mà không làm tăng backoff. Một thông tin xác thực bị từ chối (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) hoặc một scope còn thiếu là dấu chấm hết: cầu nối ngừng thử lại cho tới khi có thông tin xác thực mới.

Cầu nối được viết dựa trên bề mặt giàu tính năng nhất rồi hạ cấp theo từng năng lực.

Năng lực Slack Hành vi khi thiếu
streaming có Toàn bộ phản hồi được đăng một lần, ở cuối
streamingRequiresThread có Một đích phản hồi không có điểm neo luồng sẽ đăng nguyên phản hồi
ephemeralMessages có Câu trả lời cho lệnh slash không có handle phản hồi dùng được sẽ bị bỏ (các lời từ chối luôn được đăng trong luồng)
threadStatus có Không có dòng “is thinking…” tạm thời
threadTitle có Luồng chat giữ tiêu đề mặc định của nó
slashCommands có Không có bề mặt /cc
taskCards có Chỉ văn bản — không có thẻ phía trên phản hồi và không có liên kết “View in Control Center”
maxMessageLength 39.000 Những phản hồi dài hơn bị cắt bớt kèm một con trỏ tới Control Center
maxStreamChunkLength 3.800 Các lần nối thêm được tách tại ranh giới đoạn, dòng hoặc khoảng trắng

Quảng cáo truyền theo luồng không phải là cam kết truyền theo luồng: tính năng của Slack cần có Agents & AI Apps trên một gói trả phí. Một lời từ chối tại thời điểm gọi (streaming_not_supported, feature_not_enabled, paid_only, …) được ghi nhớ trong suốt vòng đời của kết nối và được phơi ra dưới dạng streamingAvailable: false. Thẻ công việc của Slack đi cùng lệnh gọi đó, nên một Slack từ chối chế độ hiển thị của thẻ vẫn giữ được luồng và chỉ mất thẻ, một lần, trong suốt vòng đời của kết nối.

Một luồng cũng phải nêu tên người đọc mà nó hướng tới. Slack đòi cả người nhận lẫn team của người đó để mở luồng trong một kênh (tin nhắn riêng thì không) và từ chối bằng missing_recipient_user_id / missing_recipient_team_id nếu thiếu — một lời từ chối theo từng lần gọi, nên cầu nối vẫn tiếp tục thử và trong lúc đó lượt trả về dưới dạng một phản hồi được đăng trọn vẹn. Người nhận là người đã hỏi, lấy từ tin nhắn đi vào (team của họ, không phải của ứng dụng, nhờ vậy một kênh Slack Connect được phân giải đúng). Với một lượt không ai khởi tạo từ chat — công việc được khởi động trong Control Center trên một cuộc hội thoại đã được nối — người nhận là thành viên có liên kết đã tạo ra cầu nối; nếu từ đó họ đã hủy liên kết tài khoản chat của mình thì lượt đó được đăng trọn vẹn thay vì truyền theo luồng.

Một thẻ được nhóm lại cho mỗi lượt của agent (task_display_mode: plan), với một dòng cho mỗi bước. Mỗi dòng là một task_update gồm id + title + status, cộng thêm một details dùng một lần khi suy nghĩ đã sẵn sàng. details và output cùng id được nối liền không có dấu phân cách, nên details chỉ được gửi một lần và output không bao giờ được truyền theo luồng. Tiêu đề của kế hoạch là yêu cầu; câu trả lời là một lần nối thêm riêng, muộn hơn, mang một khối markdown_text.

Phần Nội dung
Tiêu đề kế hoạch Điều đã được hỏi
Dòng thiết lập Working on it…, rồi Cloning …, rồi Starting the agent… — một dòng, tiêu đề được thay thế
Dòng suy nghĩ Thinking…, được thêm khi agent bắt đầu suy luận; suy nghĩ chính là details của dòng này, được gửi một lần khi suy luận kết thúc
Các dòng công cụ Một dòng cho mỗi công cụ (Bash which swift), id duy nhất để chúng nằm cạnh nhau
Trạng thái Đang thực hiện khi dòng đó chạy; hoàn tất khi dòng kế tiếp bắt đầu hoặc lượt kết thúc; lỗi khi thất bại, bị ngắt hoặc chạm giới hạn lượt
Lời kêu gọi hành động View in Control Center như một dòng cuối khi lượt kết thúc — vắng mặt khi công việc còn đang chạy và vắng mặt khi máy chủ không có URL công khai truy cập được

Những gì một thẻ không bao giờ mang: đầu ra công cụ, một đối số nằm ngoài danh sách cho phép đó, hay bất cứ thứ gì đọc từ cơ sở dữ liệu. Phần văn xuôi suy luận chỉ là details của dòng Thinking… (giới hạn 256 ký tự), không phải phần thân tin nhắn. Một thẻ không nói thêm điều gì mới sẽ không được gửi lại. Một lượt nhắc mở luồng ngay lập tức để luồng Slack không im lặng; một lượt sau đó không tạo ra chút văn bản nào sẽ kết thúc chính thẻ đó bằng một ghi chú ngắn thay vì đăng tin nhắn thứ hai. Một lượt không lời trên một space chưa từng mở luồng (không có thẻ, hoặc đã bị từ chối truyền theo luồng) vẫn đăng một tin nhắn mang thẻ đã hoàn tất và ghi chú đó.

Thẻ được truyền theo luồng và thẻ được đăng là hai payload khác nhau:

Dạng Hình dạng Văn xuôi
Truyền theo luồng plan_update cộng các khối task_update có khóa id Chuỗi thuần, giới hạn 256 ký tự mỗi khối
Đăng trọn Một khối plan bọc các task_card có khóa task_id Văn bản giàu định dạng

Xử lý lỗi:

Tình huống Hành vi
Nhà cung cấp từ chối thẻ trong một lần nối thêm Lần nối thêm được thử lại ngay mà không có thẻ; văn xuôi vẫn đến trực tiếp và thẻ bị bỏ trong suốt vòng đời của kết nối
Nhà cung cấp cũng từ chối cả văn xuôi Luồng bị đóng và phản hồi được đăng trọn vẹn
Luồng đứt sau khi một phần phản hồi đã đến Luồng bị đóng và không có gì được đăng lại

Xem Cầu chat để hiểu vì sao payload được chia theo cách này.

Thẻ mở ra khi một lượt nhắc được chấp nhận, trước khi clone và trước token đầu tiên; lượt tiếp theo dùng lại chính thẻ và luồng đó. Phần tường thuật sau đó đến từ SpaceProvisioningChanged trên bus sự kiện miền — cùng tiến trình mà banner cấp phát của ứng dụng desktop đọc từ hàng của space.

Trạng thái cấp phát Tường thuật
Đã chấp nhận lượt nhắc Working on it…
Bước repo Cloning <repo>…
Bước prCheckout Checking out the pull request in <repo>…
Bước script Running the setup script for <repo>…
Bước agent Setting up <agent>…
provisioning, chưa có bước nào Preparing the workspace…
ready Starting the agent…
Agent đang suy luận Thinking…
failed Workspace setup failed — details are in Control Center.
cancelled Workspace setup was stopped — details are in Control Center.

Tường thuật thiết lập cần đủ cả bốn điều kiện:

Điều kiện
Nhà cung cấp hiển thị được thẻ
Truyền theo luồng khả dụng trên kết nối này
Space đang có một yêu cầu chat chờ xử lý
Chưa có thẻ nào đang báo cáo một lượt ở đó

Yêu cầu vẫn chờ xử lý cho tới khi một lượt bắt đầu hoặc thiết lập thất bại. Một sự kiện ready không kèm thẻ nào đã mở là một space đã ấm sẵn, nên được để yên; còn việc cấp phát mà không ai trong chat đang chờ — một cuộc hội thoại được mở trong ứng dụng, hay một cuộc hội thoại bị bỏ dở được tiếp tục lúc khởi động — không tạo ra thẻ nào cả.

Các sản phẩm chat chỉ chấp nhận http(s) trong một liên kết và không gì khác, nên một lời kêu gọi hành động không thể nêu thẳng scheme của ứng dụng desktop.

Thuộc tính Giá trị
Tuyến GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id>
Nguồn gốc Được suy ra từ URL công khai của máy chủ (ws → http, wss → https); loopback được giữ nguyên
Phản hồi Một trang HTML no-store nhỏ, điều hướng tới control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> và hiển thị một nút để mở thủ công
Kiểm tra Id phải khớp [A-Za-z0-9_.-]{1,128}; mọi thứ khác đều là 404
Truy cập Công khai và vô hại — nó không đọc cơ sở dữ liệu nào, nên không phải một oracle kiểm tra sự tồn tại; ứng dụng mà nó mở ra mới là bên thực hiện ủy quyền
Trường Giới hạn Ghi chú
appName 1–35 ký tự Mặc định là Control Center · <workspace name>
botDisplayName 1–80 ký tự Mặc định là control-center
description ≤ 140 ký tự
agentDescription ≤ 300 ký tự Được hiển thị phía trên cuộc trò chuyện riêng với bot
command ≤ 32 ký tự Được chuẩn hóa: bỏ dấu gạch chéo ở đầu, chuyển thành chữ thường, loại bỏ [^a-z0-9_-], để trống thì quay về cc
agentEnabled — Liệu trải nghiệm tin nhắn riêng/agent có được phơi ra hay không

Một ô để trống nghĩa là “để nguyên”, chứ không phải “xóa đi”. Các giới hạn là của Slack, vốn là những giới hạn chặt nhất trong số các sản phẩm được mô hình hóa.

Thuộc tính Giá trị
Độ dài 6 ký tự
Bảng chữ cái 23456789ABCDEFGHJKLMNPQRSTVWXYZ (không có 0/O, 1/I, U)
Thời gian sống 15 phút
Số lần dùng Một
Lưu trữ Trong bộ nhớ — khởi động lại máy chủ sẽ làm vô hiệu các mã còn hiệu lực
Phạm vi Một không gian làm việc và một nhà cung cấp; việc tạo mã mới sẽ làm vô hiệu mã trước đó của thành viên

Việc đổi mã được đối chiếu không phân biệt chữ hoa chữ thường. Mã không xác định, đã hết hạn, thuộc không gian làm việc khác và dành cho nhà cung cấp khác đều thất bại giống hệt nhau một cách có chủ đích.

Phương thức Giá trị truyền Cách thức
Mã code Thành viên đã đổi /cc link <code>
Email email Email đã xác minh của nhà cung cấp khớp với một người dùng vốn đã là thành viên của không gian làm việc

Tất cả đều thuộc phạm vi không gian làm việc. Máy chủ là phi trạng thái, nên mỗi lời gọi mang workspace_id của riêng nó trong args; bộ điều phối từ chối một thao tác thuộc phạm vi không gian làm việc mà thiếu trường đó, trước khi bất kỳ cơ sở dữ liệu nào mở ra. Thông tin xác thực chỉ đi vào — các lần đọc trạng thái không bao giờ trả chúng ra.

Thao tác Loại Vai trò tối thiểu Các lớp hành động
chat.providers đọc guest —
chat.status đọc guest —
chat.connect thay đổi admin secretAccess, networkEgress
chat.disconnect phá hủy admin secretAccess
chat.listUserLinks đọc guest —
chat.beginUserLink thay đổi member —
chat.unlinkUser thay đổi member cho chính bạn, admin cho người khác —
chat.createApp thay đổi admin secretAccess, networkEgress
chat.setupLink đọc admin —
chat.botProfile đọc admin networkEgress
chat.updateBotProfile thay đổi admin networkEgress

Các thao tác đọc thừa hưởng mức sàn guest từ loại của chúng; các thao tác thay đổi không có mức sàn tường minh thì đòi hỏi member.

chat.setupLink dựng một URL tạo ứng dụng của nhà cung cấp từ hồ sơ bot. Nó không khai báo lớp hành động nào vì không đọc secret và không gọi ra đâu cả — chính trình duyệt của người dùng thực hiện lời gọi duy nhất.

Một truy vấn phản ứng hoàn thiện bề mặt này:

Truy vấn Loại Phát ra
chat.watchUserLinks theo dõi Danh sách thành viên, được đẩy lại mỗi khi có liên kết được tạo hoặc gỡ

Nó tồn tại vì việc liên kết diễn ra ở phía bên kia: thành viên gõ mã dùng một lần vào bot, nên không phản hồi nào từ máy khách có thể mang kết quả trở về. Danh sách trong phần cài đặt và hộp thoại “liên kết tài khoản của tôi” đang mở đều theo dõi nó, và hộp thoại chuyển sang trạng thái xác nhận ngay khi liên kết của chính thành viên được ghi nhận.

chat.beginUserLink luôn tạo một mã cho chính danh tính của người gọi. chat.updateBotProfile chỉ trả về remaining_step khi còn một bước chưa xong (thường là cài đặt lại); việc nó vắng mặt nghĩa là thay đổi đã có hiệu lực.

chat.providers trả về một mục cho mỗi nhà cung cấp được cung cấp — descriptor cộng thêm, khi có, trạng thái kết nối. Mọi thứ mà giao diện cài đặt hiển thị đều đến từ payload đó, và đó là lý do vì sao thêm một nhà cung cấp chỉ là một thay đổi phía máy chủ.

Nội dung Vị trí
Thông tin xác thực kết nối <dataDir>/<workspaceId>/chat_credentials/<provider>.json
Thiết lập có hướng dẫn còn dang dở <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json
Các tệp Slack cũ <dataDir>/<workspaceId>/slack_credentials.json và slack_app_setup.json, được đọc một lần và migration tại chỗ

Thông tin xác thực nằm bên cạnh cơ sở dữ liệu của không gian làm việc chứ không nằm trong đó: xóa một không gian làm việc là mang theo cả token chat của nó, còn workspace.export hay một bản sao lưu chỉ sao chép workspace.db — nên một không gian làm việc được chuyển cho người khác không mang theo token bot còn sống. Việc ghi là nguyên tử (tệp tạm cộng đổi tên) và được siết lại chỉ cho chủ sở hữu.

Cả hai đều nằm trong cơ sở dữ liệu của không gian làm việc, vốn mang một đường cơ sở v1 đã được gộp gọn — nên một phép đọc xuyên không gian làm việc sẽ không biên dịch được.

chat_space_links — một cuộc hội thoại bên ngoài ↔ một space Control Center:

Cột Ghi chú
id UUID v4
workspace_id Phạm vi không gian làm việc
provider ChatProvider.wire
external_team_id Team Slack T…, để trống với những nhà cung cấp không có khái niệm này
external_channel_id Kênh Slack C…, nhóm G…, tin nhắn riêng D…
external_thread_id Điểm neo luồng Slack, hoặc null khi cả cuộc hội thoại là điểm neo (tin nhắn riêng với bot)
cc_space_id REFERENCES spaces(id) ON DELETE CASCADE
created_by_user_id, created_at, last_activity_at

Duy nhất trên (workspace_id, provider, external_channel_id, external_thread_id), kèm một chỉ mục bộ phận áp đặt điều tương tự cho các thread id null (SQLite coi các NULL là khác nhau).

chat_user_links — một thành viên bên ngoài ↔ một người dùng Control Center:

Cột Ghi chú
id UUID v4
workspace_id Phạm vi không gian làm việc
provider ChatProvider.wire
external_team_id, external_user_id Thành viên Slack U…
user_id Người dùng Control Center (toàn cục, nên không có khóa ngoại)
method code hoặc email
linked_at

Duy nhất trên (workspace_id, provider, external_team_id, external_user_id) và trên (workspace_id, provider, user_id) — quy kết đơn trị theo cả hai hướng, trong khi một người có thể được liên kết trên nhiều nhà cung cấp.

Một liên kết là phân giải danh tính, không phải một sự cấp quyền truy cập: tư cách thành viên được kiểm tra lại ở mọi sự kiện đi vào, nên một liên kết trỏ tới thành viên đã bị gỡ từ đó sẽ phân giải thành một lời từ chối.

Một space đã nối được tạo với SpaceKind của nhà cung cấp (slack). Nó hành xử như một space thông thường dành cho con người — được thông báo và xếp nhóm như topic — và isExternalChat là phép kiểm tra mà mọi hành vi (phản chiếu, gắn huy hiệu, lưu giữ) đều đi qua.

Mọi tin nhắn mà cầu nối đưa vào đều mang một dấu metadata['chat']:

{
"chat": {
"provider": "slack",
"teamId": "T024BE7LD",
"channelId": "C0G9QF9GW",
"threadId": "1700000000.000100",
"messageId": "1700000000.000200",
"userId": "U024BE7LH",
"userLabel": "sam"
}
}

Dấu này cũng chính là lá chắn chống tiếng vọng: bản phản chiếu hướng ra bỏ qua mọi tin nhắn mang nó, và chính điều đó ngăn một tin nhắn chat đi vào bị đăng thẳng trở lại.

Hành vi Chi tiết
Những gì được chuyển ra Chỉ văn bản của agent, cộng dòng trạng thái của thẻ công việc. Phần văn xuôi suy luận và đầu ra công cụ ở lại Control Center
Nhịp truyền theo luồng Được điều tiết còn một lần đẩy mỗi giây trong lúc một lượt chạy; thẻ và đoạn văn bản mà nó giải thích đi trong cùng một lời gọi
Tin nhắn do con người gõ trong Control Center Được phản chiếu vào luồng đã liên kết, với tiền tố là tên hiển thị của tác giả
Các lượt rỗng Được báo bằng một ghi chú ngắn (lần chạy thất bại, bị ngắt hoặc dừng ở giới hạn lượt) thay vì im lặng
Khử trùng lặp Được khóa theo id sự kiện của chính nhà cung cấp; 512 mục gần nhất được ghi nhớ
Agent cửa trước Một cuộc hội thoại đã nối mới sẽ đánh thức agent CEO của không gian làm việc, nếu không có thì agent đầu tiên, nếu cũng không thì không ai cả
Đặt tên space #channel · <request> cho một luồng kênh Slack, Slack DM · <request> cho tin nhắn riêng, tiêu đề giới hạn ở 48 ký tự
Cô lập lỗi Một sự kiện hỏng không bao giờ làm sập cầu nối; kết nối vẫn phục vụ mọi cuộc hội thoại khác