Jembatan chat
Penyedia
Bagian berjudul “Penyedia”| Penyedia | Nilai wire | Transport | Jenis space |
|---|---|---|---|
| Slack | slack |
Socket Mode (outbound WSS) | slack |
Slack adalah satu-satunya penyedia. ChatProvider adalah enum bernilai tunggal dan cc_server mendaftarkan satu plugin. Skema, setiap pencarian, dan setiap indeks unik dikunci berdasarkan provider, sehingga id tidak pernah melintasi penyedia dan menambahkan penyedia baru hanyalah perubahan di sisi server.
Satu aplikasi di sisi penyedia per ruang kerja × penyedia.
Perintah chat
Bagian berjudul “Perintah chat”Nama perintah bersifat per aplikasi (cc secara bawaan, jadi /cc). Admin dapat menggantinya; jembatan lalu memakai nama terakhir yang dipakai untuk memanggilnya.
| Perintah | Efek |
|---|---|
/cc help (atau /cc tanpa verba) |
Menampilkan daftar yang bisa dilakukan bot |
/cc link <code> |
Menukarkan kode sekali pakai dan menautkan identitas chat pengirim |
/cc ticket <title> |
Membuat tiket lokal yang dilaporkan oleh anggota yang tertaut |
/cc ticket <title> | <description> |
Sama, dengan semua teks setelah | pertama sebagai deskripsi |
| Verba tidak dikenal | Melaporkan verba yang tidak dikenal beserta teks bantuan |
Cara menjangkau agen:
| Tindakan | Perilaku |
|---|---|
@bot … di sebuah kanal |
Membuka thread Slack dan space Control Center yang cocok; agen membalas di thread Slack |
| Balasan di thread Slack yang dijembatani | Melanjutkan space Control Center yang sama — tidak perlu sebutan |
| Pesan biasa di sebuah kanal | Diabaikan (tidak ditujukan ke bot dan bukan di thread Slack yang dijembatani) |
| DM ke bot | Satu space Control Center per percakapan DM; menetapkan judul thread Slack dan status “is thinking…” selama agen bekerja |
Kolom kredensial (Slack)
Bagian berjudul “Kolom kredensial (Slack)”Dideklarasikan oleh deskriptor penyedia, yang menghasilkan dialog koneksi dan memvalidasi kiriman.
| Id kolom | Label | Wajib | Prefiks | Letaknya |
|---|---|---|---|---|
botToken |
Token bot | Ya | xoxb- |
OAuth & Permissions |
appToken |
Token tingkat aplikasi | Ya | xapp- |
Basic Information → App-Level Tokens, scope connections:write |
configRefreshToken |
Token konfigurasi aplikasi | Tidak | xoxe |
App configuration tokens on api.slack.com/apps |
configRefreshToken adalah kredensial pengelolaan aplikasi: tanpa itu koneksi tetap berfungsi, tetapi bot tidak dapat dibuat atau diedit dari Control Center. Nilainya berotasi setiap kali dipakai dan nilai hasil rotasinya langsung disimpan.
Nilai dipangkas, kunci yang tidak dideklarasikan dibuang, dan ketidakcocokan prefiks ditolak dengan kalimat yang sama di klien dan di server. Kolom wajib harus ada pada setiap koneksi; kolom opsional yang dibiarkan kosong mempertahankan nilai yang sudah tersimpan.
Persyaratan aplikasi Slack
Bagian berjudul “Persyaratan aplikasi Slack”Cakupan token bot:
app_mentions:readassistant:writechannels:historychat:writecommandsgroups:historyim:historyusers:readusers:read.emailLangganan event bot:
app_home_openedapp_mentionmessage.channelsmessage.groupsmessage.imPengaturan struktural yang dipaksakan Control Center saat membuat atau mengedit aplikasi: Socket Mode aktif, tanpa URL permintaan, tab Messages aktif dan dapat ditulis, bot always_online, cakupan dan event yang diperlukan digabungkan ke apa pun yang sudah dimiliki aplikasi (tidak pernah dikurangi), dan satu perintah slash yang membawa deskripsi File a ticket or link your Control Center account — penanda yang mengidentifikasi perintah milik Control Center dalam manifes yang diekspor.
Status koneksi
Bagian berjudul “Status koneksi”| Status | Wire | Arti |
|---|---|---|
| Tidak terhubung | disconnected |
Tidak ada kredensial tersimpan untuk ruang kerja × penyedia ini |
| Menghubungkan… | connecting |
Kredensial tersimpan, transport sedang menghubungi atau menghubungi ulang |
| Aktif | connected |
Transport aktif dan menerima event |
| Kesalahan koneksi | error |
Tersimpan tetapi gagal; galat dari penyedia itu sendiri ditampilkan berdampingan |
Koneksi ulang memakai backoff eksponensial dengan jitter, dibatasi maksimum 30 detik. Rotasi socket yang diprakarsai penyedia akan segera tersambung ulang tanpa menaikkan backoff. Kredensial yang ditolak (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) atau cakupan yang hilang bersifat final: jembatan berhenti mencoba ulang sampai kredensial baru datang.
Kemampuan penyedia (Slack)
Bagian berjudul “Kemampuan penyedia (Slack)”Jembatan ini ditulis terhadap permukaan fitur yang terkaya dan menurunkan perilakunya sesuai kemampuan yang tersedia.
| Kemampuan | Slack | Efek saat tidak tersedia |
|---|---|---|
streaming |
ya | Seluruh balasan diposkan sekali, di akhir |
streamingRequiresThread |
ya | Target balasan tanpa jangkar thread memposkan balasan secara utuh |
ephemeralMessages |
ya | Jawaban perintah slash tanpa pegangan balasan yang dapat dipakai akan dibuang (penolakan selalu diposkan di dalam thread) |
threadStatus |
ya | Tidak ada baris sementara “is thinking…” |
threadTitle |
ya | Thread chat mempertahankan judul bawaannya |
slashCommands |
ya | Tidak ada permukaan /cc |
taskCards |
ya | Hanya teks — tidak ada kartu di atas balasan dan tidak ada tautan “View in Control Center” |
maxMessageLength |
39,000 | Balasan yang lebih panjang dipotong dengan penunjuk ke Control Center |
maxStreamChunkLength |
3,800 | Penambahan dipisah pada batas paragraf, baris, atau spasi |
Streaming yang diiklankan belum tentu streaming yang dijanjikan: streaming Slack memerlukan fitur Agents & AI Apps pada paket berbayar. Penolakan pada saat pemanggilan (streaming_not_supported, feature_not_enabled, paid_only, …) diingat selama koneksi itu hidup dan dimunculkan sebagai streamingAvailable: false. Kartu tugas Slack menumpang pemanggilan yang sama, sehingga Slack yang menolak mode tampilan kartu tetap mempertahankan streamnya dan hanya kehilangan kartunya, sekali saja, selama koneksi itu hidup.
Sebuah stream juga harus menyebutkan pembaca yang dituju. Slack mensyaratkan adanya pengguna penerima sekaligus timnya untuk membukanya di sebuah kanal (DM tidak bisa), dan jika tidak ada akan menolak dengan missing_recipient_user_id / missing_recipient_team_id — penolakan per pemanggilan, sehingga jembatan terus mencoba dan sementara itu giliran tersebut tiba sebagai satu balasan yang diposkan. Penerimanya adalah siapa pun yang bertanya, diambil dari pesan masuk (tim mereka, bukan tim aplikasi, sehingga kanal Slack Connect terselesaikan dengan benar). Untuk giliran yang tidak dimulai siapa pun dari chat — pekerjaan yang dijalankan di Control Center pada percakapan yang sudah dijembatani — penerimanya adalah anggota yang tautannya membuat jembatan itu; jika sejak itu mereka membatalkan tautan akun chatnya, giliran tersebut diposkan secara utuh alih-alih distreaming.
Kartu tugas
Bagian berjudul “Kartu tugas”Satu kartu terkelompok per giliran agen (task_display_mode: plan), dengan satu baris per langkah. Setiap baris adalah task_update dari id + title + status, ditambah details sekali jalan saat pemikirannya siap. details dan output dengan id yang sama disambung tanpa pemisah, jadi details dikirim sekali dan output tidak pernah distreaming. Judul rencananya adalah permintaan itu; jawabannya adalah penambahan terpisah di kemudian hari yang membawa potongan markdown_text.
| Bagian | Isi |
|---|---|
| Judul rencana | Apa yang diminta |
| Baris penyiapan | Working on it…, lalu Cloning …, lalu Starting the agent… — satu baris, judulnya diganti |
| Baris berpikir | Thinking…, ditambahkan saat agen mulai bernalar; pemikirannya adalah details baris ini, dikirim sekali saat penalaran selesai |
| Baris alat | Satu per alat (Bash which swift), id unik agar duduk berdampingan |
| Status | Sedang berlangsung selama baris itu berjalan; selesai saat baris berikutnya mulai atau giliran berakhir; error untuk gagal, terputus, dan batas giliran |
| Ajakan bertindak | View in Control Center sebagai baris penutup saat giliran berakhir — tidak ada selama pekerjaan masih berjalan dan tidak ada saat server tidak punya URL publik yang dapat dijangkau |
Yang tidak pernah dibawa kartu: keluaran alat, argumen di luar daftar izin itu, atau apa pun yang dibaca dari basis data. Prosa penalaran hanyalah details pada baris Thinking… (dibatasi 256 karakter), bukan isi pesannya. Kartu yang tidak akan mengatakan hal baru tidak dikirim ulang. Sebutan langsung membuka stream agar thread Slack tidak sepi; giliran yang kemudian tidak menghasilkan teks sama sekali akan menutup kartu yang sama dengan catatan singkat alih-alih memposkan pesan kedua. Giliran tanpa kata di space yang tidak pernah membuka stream (tanpa kartu, atau streaming sudah ditolak) tetap memposkan satu pesan yang membawa kartu yang sudah selesai dan catatannya.
Kartu yang distreaming dan yang diposkan adalah payload yang berbeda:
| Bentuk | Wujud | Prosa |
|---|---|---|
| Distreaming | plan_update plus potongan task_update dengan kunci id |
String biasa, dibatasi 256 karakter per potongan |
| Diposkan | Blok plan yang membungkus task_card dengan kunci task_id |
Teks kaya |
Penanganan kegagalan:
| Situasi | Perilaku |
|---|---|
| Penyedia menolak kartu pada sebuah penambahan | Penambahan itu langsung dicoba ulang tanpa kartu; prosanya tetap tiba secara live dan kartu dibuang selama koneksi itu hidup |
| Penyedia juga menolak prosanya | Stream ditutup dan balasannya diposkan secara utuh |
| Stream terputus setelah sebagian balasan tiba | Stream ditutup dan tidak ada yang diposkan ulang |
Lihat Jembatan chat untuk alasan payload dipisah seperti ini.
Penyiapan, sebelum ada giliran
Bagian berjudul “Penyiapan, sebelum ada giliran”Kartu terbuka saat sebuah sebutan diterima, sebelum kloning dan sebelum token pertama; giliran berikutnya memakai kartu dan stream yang sama. Narasi setelahnya berasal dari SpaceProvisioningChanged di bus peristiwa domain — kemajuan yang sama yang dibaca banner penyiapan desktop dari baris space.
| Status penyiapan | Narasi |
|---|---|
| Sebutan diterima | Working on it… |
Langkah repo |
Cloning <repo>… |
Langkah prCheckout |
Checking out the pull request in <repo>… |
Langkah script |
Running the setup script for <repo>… |
Langkah agent |
Setting up <agent>… |
provisioning, belum ada langkah |
Preparing the workspace… |
ready |
Starting the agent… |
| Agen bernalar | Thinking… |
failed |
Workspace setup failed — details are in Control Center. |
cancelled |
Workspace setup was stopped — details are in Control Center. |
Narasi penyiapan memerlukan keempat syarat berikut:
| Syarat |
|---|
| Penyedia merender kartu |
| Streaming tersedia pada koneksi ini |
| Space memiliki permintaan chat yang sedang berjalan |
| Belum ada kartu yang melaporkan giliran di sana |
Permintaan itu tetap berjalan sampai giliran dimulai atau penyiapan gagal. Event ready tanpa kartu yang sudah terbuka adalah space hangat dan dibiarkan sepi, dan penyiapan yang tidak ditunggu siapa pun di chat — percakapan yang dibuka di aplikasi, percakapan telantar yang dilanjutkan saat boot — tidak menghasilkan kartu sama sekali.
Halaman pengalihan /open
Bagian berjudul “Halaman pengalihan /open”Produk chat hanya menerima http(s) di dalam tautan dan tidak yang lain, jadi ajakan bertindak tidak bisa menyebut skema desktop secara langsung.
| Properti | Nilai |
|---|---|
| Rute | GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id> |
| Origin | Diturunkan dari URL publik server (ws → http, wss → https); loopback dipertahankan |
| Respons | Halaman HTML kecil tanpa cache yang menavigasi ke control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> dan menampilkan tombol manual |
| Validasi | Id harus cocok dengan [A-Za-z0-9_.-]{1,128}; selain itu adalah 404 |
| Akses | Publik dan inert — tidak membaca basis data apa pun, jadi bukan oracle keberadaan; aplikasi yang dibukanya yang melakukan otorisasi |
Kolom profil bot
Bagian berjudul “Kolom profil bot”| Kolom | Batas | Catatan |
|---|---|---|
appName |
1–35 karakter | Bawaannya Control Center · <workspace name> |
botDisplayName |
1–80 karakter | Bawaannya control-center |
description |
≤ 140 karakter | |
agentDescription |
≤ 300 karakter | Ditampilkan di atas DM dengan bot |
command |
≤ 32 karakter | Dinormalisasi: garis miring di depan dihapus, dijadikan huruf kecil, [^a-z0-9_-] dibuang, jika kosong kembali ke cc |
agentEnabled |
— | Apakah pengalaman DM/agen tersedia |
Kotak kosong berarti “biarkan apa adanya”, bukan “kosongkan”. Batasnya adalah batas milik Slack, yang paling ketat di antara produk yang dimodelkan.
Kode tautan
Bagian berjudul “Kode tautan”| Properti | Nilai |
|---|---|
| Panjang | 6 karakter |
| Alfabet | 23456789ABCDEFGHJKLMNPQRSTVWXYZ (tanpa 0/O, 1/I, U) |
| Masa berlaku | 15 menit |
| Penggunaan | Sekali |
| Penyimpanan | Di memori — mulai ulang server membatalkan kode yang masih berlaku |
| Cakupan | Satu ruang kerja dan satu penyedia; membuat kode baru membatalkan kode anggota sebelumnya |
Penukaran dicocokkan tanpa membedakan huruf besar/kecil. Kode yang tidak dikenal, kedaluwarsa, milik ruang kerja lain, dan penyedia yang salah semuanya gagal dengan cara yang sama secara sengaja.
Metode tautan
Bagian berjudul “Metode tautan”| Metode | Wire | Cara |
|---|---|---|
| Kode | code |
Anggota menukarkan /cc link <code> |
email |
Email terverifikasi dari penyedia cocok dengan pengguna yang sudah menjadi anggota ruang kerja |
Operasi RPC
Bagian berjudul “Operasi RPC”Semuanya bercakupan ruang kerja. Server bersifat stateless, jadi setiap panggilan membawa workspace_id-nya sendiri di args; dispatcher menolak operasi bercakupan ruang kerja yang menghilangkannya, sebelum basis data apa pun dibuka. Kredensial hanya bergerak masuk — pembacaan status tidak pernah mengembalikannya.
| Operasi | Jenis | Peran minimum | Kelas tindakan |
|---|---|---|---|
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 untuk diri sendiri, admin untuk siapa pun yang lain |
— |
chat.createApp |
mutate | admin |
secretAccess, networkEgress |
chat.setupLink |
read | admin |
— |
chat.botProfile |
read | admin |
networkEgress |
chat.updateBotProfile |
mutate | admin |
networkEgress |
Operasi baca mewarisi batas bawah guest dari jenisnya; mutasi tanpa batas bawah eksplisit memerlukan member.
chat.setupLink menyusun URL pembuatan aplikasi penyedia dari profil bot. Operasi ini tidak mendeklarasikan kelas tindakan karena tidak membaca rahasia apa pun dan tidak menghubungi apa pun — hanya browser pengguna sendiri yang melakukan panggilan.
Satu kueri reaktif melengkapi permukaan ini:
| Kueri | Jenis | Memancarkan |
|---|---|---|
chat.watchUserLinks |
watch | Daftar anggota, dikirim ulang setiap kali tautan dibuat atau dihapus |
Kueri ini ada karena penautan terjadi di sisi lain: anggota mengetikkan kode sekali pakai ke dalam bot, sehingga tidak ada respons klien yang dapat membawa jawabannya kembali. Daftar di pengaturan dan dialog “hubungkan akun saya” yang terbuka keduanya mengikuti kueri ini, dan dialognya beralih ke konfirmasi begitu tautan milik anggota itu tiba.
chat.beginUserLink selalu membuat kode untuk identitas pemanggil itu sendiri. chat.updateBotProfile mengembalikan remaining_step hanya ketika masih ada langkah tersisa (biasanya instal ulang); tidak adanya nilai itu berarti editan sudah berlaku.
chat.providers mengembalikan satu entri per penyedia yang ditawarkan — deskriptornya ditambah, bila ada, status koneksi. Semua yang dirender UI pengaturan berasal dari payload itu, itulah sebabnya menambahkan penyedia hanyalah perubahan di sisi server.
Penyimpanan
Bagian berjudul “Penyimpanan”| Apa | Di mana |
|---|---|
| Kredensial koneksi | <dataDir>/<workspaceId>/chat_credentials/<provider>.json |
| Penyiapan terpandu yang belum selesai | <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json |
| Berkas Slack lama | <dataDir>/<workspaceId>/slack_credentials.json dan slack_app_setup.json, dibaca sekali dan dimigrasikan di tempat |
Kredensial berada di samping basis data ruang kerja, bukan di dalamnya: menghapus ruang kerja sekaligus menghapus token chatnya dan workspace.export atau cadangan menyalin workspace.db — jadi ruang kerja yang diserahkan kepada orang lain tidak membawa token bot yang masih hidup. Penulisan bersifat atomik (berkas sementara plus rename) dan diperketat hanya untuk pemiliknya.
Tabel basis data
Bagian berjudul “Tabel basis data”Keduanya berada di basis data ruang kerja, yang membawa baseline v1 yang sudah dipadatkan — sehingga pembacaan lintas ruang kerja tidak dapat dikompilasi.
chat_space_links — satu percakapan eksternal ↔ satu space Control Center:
| Kolom | Catatan |
|---|---|
id |
UUID v4 |
workspace_id |
Cakupan ruang kerja |
provider |
ChatProvider.wire |
external_team_id |
Tim Slack T…, kosong untuk penyedia yang tidak mengenal konsep ini |
external_channel_id |
Kanal Slack C…, grup G…, DM D… |
external_thread_id |
Jangkar thread Slack, atau null ketika seluruh percakapan menjadi jangkarnya (DM bot) |
cc_space_id |
REFERENCES spaces(id) ON DELETE CASCADE |
created_by_user_id, created_at, last_activity_at |
Unik pada (workspace_id, provider, external_channel_id, external_thread_id), dengan indeks parsial yang menegakkan hal yang sama untuk thread id null (SQLite memperlakukan NULL sebagai berbeda).
chat_user_links — satu anggota eksternal ↔ satu pengguna Control Center:
| Kolom | Catatan |
|---|---|
id |
UUID v4 |
workspace_id |
Cakupan ruang kerja |
provider |
ChatProvider.wire |
external_team_id, external_user_id |
Anggota Slack U… |
user_id |
Pengguna Control Center (global, jadi tanpa kunci asing) |
method |
code atau email |
linked_at |
Unik pada (workspace_id, provider, external_team_id, external_user_id) dan pada (workspace_id, provider, user_id) — atribusi bernilai tunggal di kedua arah, sementara satu orang dapat tertaut di beberapa penyedia.
Tautan adalah resolusi identitas, bukan pemberian akses: keanggotaan diperiksa ulang pada setiap event yang masuk, jadi tautan ke anggota yang sejak itu dihapus berujung pada penolakan.
Jenis space dan metadata pesan
Bagian berjudul “Jenis space dan metadata pesan”Space yang dijembatani dibuat dengan SpaceKind milik penyedia (slack). Perilakunya seperti space biasa yang menghadap manusia — dinotifikasi dan dikelompokkan seperti topic — dan isExternalChat adalah pemeriksaan yang dilalui setiap perilaku (pencerminan, pemberian badge, retensi).
Setiap pesan yang dibawa masuk jembatan membawa stempel metadata['chat']:
{ "chat": { "provider": "slack", "teamId": "T024BE7LD", "channelId": "C0G9QF9GW", "threadId": "1700000000.000100", "messageId": "1700000000.000200", "userId": "U024BE7LH", "userLabel": "sam" }}Stempel itu juga berfungsi sebagai penjaga gema: cermin keluar melewati setiap pesan yang membawanya, itulah yang mencegah pesan chat yang masuk diposkan kembali secara langsung.
Perilaku relay
Bagian berjudul “Perilaku relay”| Perilaku | Rincian |
|---|---|
| Apa yang diteruskan keluar | Hanya teks agen, ditambah baris status kartu tugasnya. Prosa penalaran dan keluaran alat tetap di Control Center |
| Irama streaming | Dibatasi menjadi satu pengiriman per detik selama giliran berjalan; kartu dan teks yang dijelaskannya berjalan dalam panggilan yang sama |
| Pesan manusia yang diketik di Control Center | Dicerminkan ke thread yang tertaut, diawali dengan nama tampilan penulisnya |
| Giliran kosong | Dilaporkan sebagai catatan singkat (Run gagal, terputus, atau berhenti di batas gilirannya) alih-alih diam |
| Deduplikasi | Dikunci berdasarkan id event milik penyedia sendiri; 512 yang terakhir diingat |
| Agen pintu depan | Percakapan terjembatani yang baru membangunkan agen CEO ruang kerja, jika tidak ada maka agen pertamanya, jika tidak ada maka tidak ada siapa pun |
| Penamaan space | #channel · <request> untuk thread kanal Slack, Slack DM · <request> untuk DM, judul dibatasi 48 karakter |
| Isolasi kegagalan | Satu event buruk tidak pernah menjatuhkan jembatan; koneksi itu tetap melayani semua percakapan lain |
Lihat juga
Bagian berjudul “Lihat juga”- Siapkan integrasi Slack
- Tautkan akun Slack Anda
- Sesuaikan bot chat
- Jembatan chat
- Pagar pengaman untuk kelas tindakan yang dideklarasikan operasi-operasi itu