Ga naar inhoud

Chatbrug

Provider Wire-waarde Transport Soort ruimte
Slack slack Socket Mode (uitgaand WSS) slack

Slack is de enige provider. ChatProvider is een enum met één waarde en cc_server registreert één plugin. Het schema, elke opzoeking en elke unieke index zijn gesleuteld op provider, dus id’s kruisen nooit providers en er een toevoegen is een wijziging die alleen de server raakt.

Eén app aan de providerkant per werkruimte × provider.

De commandonaam geldt per app (cc standaard, dus /cc). Een beheerder kan hem hernoemen; de brug gebruikt daarna waarmee hij het laatst is aangeroepen.

Commando Effect
/cc help (of /cc zonder werkwoord) Somt op wat de bot kan
/cc link <code> Wisselt een code voor eenmalig gebruik in en koppelt de chatidentiteit van de afzender
/cc ticket <title> Maakt een lokaal ticket aan, gemeld door het gekoppelde lid
/cc ticket <title> | <description> Hetzelfde, met alles na de eerste | als beschrijving
Onbekend werkwoord Meldt het onbekende werkwoord plus de helptekst

Manieren om een agent te bereiken:

Gebaar Gedrag
@bot … in een kanaal Opent een Slack-thread en een bijpassende Control Center-ruimte; de agent antwoordt in de Slack-thread
Antwoord in een overbrugde Slack-thread Zet dezelfde Control Center-ruimte voort — geen vermelding nodig
Gewoon bericht in een kanaal Genegeerd (niet aan de bot gericht en niet in een overbrugde Slack-thread)
DM aan de bot Eén Control Center-ruimte per DM-gesprek; zet een Slack-threadtitel en een status “is thinking…” terwijl de agent werkt

Gedeclareerd door de descriptor van de provider; die genereert het verbindingsdialoogvenster en valideert de inzending.

Veld-id Label Verplicht Prefix Waar het staat
botToken Bot-token Ja xoxb- OAuth & Permissions
appToken App-token Ja xapp- Basic Information → App-Level Tokens, scope connections:write
configRefreshToken App-configuratietoken Nee xoxe App configuration tokens op api.slack.com/apps

configRefreshToken is het inloggegeven voor app-beheer: zonder dit werkt de verbinding, maar kan de bot niet vanuit Control Center worden aangemaakt of bewerkt. Het roteert bij elk gebruik en de geroteerde waarde wordt meteen opgeslagen.

Waarden worden getrimd, niet-gedeclareerde sleutels vallen weg en een verkeerd prefix wordt op de client en de server met dezelfde zin geweigerd. Verplichte velden moeten bij elke verbinding aanwezig zijn; een optioneel veld dat leeg blijft, behoudt wat er al was opgeslagen.

Scopes van het bottoken:

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

Gebeurtenisabonnementen van de bot:

app_home_opened
app_mention
message.channels
message.groups
message.im

Structurele instellingen die Control Center afdwingt wanneer het de app aanmaakt of bewerkt: Socket Mode ingeschakeld, geen request-URL, het tabblad Messages ingeschakeld en beschrijfbaar, bot always_online, de vereiste scopes en gebeurtenissen samengevoegd met wat de app al heeft (nooit afgetrokken) en één slash-commando met de beschrijving File a ticket or link your Control Center account — de markering waaraan Control Center zijn eigen commando in een geëxporteerd manifest herkent.

Status Wire Betekenis
Niet verbonden disconnected Geen inloggegevens opgeslagen voor deze werkruimte × provider
Verbinden… connecting Inloggegevens opgeslagen, transport kiest of kiest opnieuw verbinding
Live connected Transport staat en ontvangt gebeurtenissen
Verbindingsfout error Opgeslagen maar mislukt; de eigen fout van de provider staat ernaast

Opnieuw verbinden gebeurt met exponentiële backoff en jitter, afgetopt op 30 seconden. Een door de provider gestarte socketrotatie maakt direct opnieuw verbinding zonder de backoff op te voeren. Een geweigerd inloggegeven (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) of een ontbrekende scope is definitief: de brug stopt met opnieuw proberen tot er nieuwe inloggegevens zijn.

De brug is geschreven tegen het rijkste oppervlak en schaalt per capaciteit terug.

Capaciteit Slack Effect als ze ontbreekt
streaming ja Het hele antwoord wordt één keer geplaatst, aan het einde
streamingRequiresThread ja Een antwoorddoel zonder thread-anker plaatst hele antwoorden
ephemeralMessages ja Een antwoord op een slash-commando zonder bruikbare antwoordhandle wordt weggegooid (weigeringen worden altijd in de thread geplaatst)
threadStatus ja Geen vluchtige regel “is thinking…”
threadTitle ja De chatthread behoudt zijn standaardtitel
slashCommands ja Geen /cc-oppervlak
taskCards ja Alleen tekst — geen kaart boven het antwoord en geen link “View in Control Center”
maxMessageLength 39.000 Langere antwoorden worden afgekapt, met een verwijzing naar Control Center
maxStreamChunkLength 3.800 Aanvullingen worden gesplitst op een alinea-, regel- of spatiegrens

Een capaciteit adverteren is die nog niet beloven: die van Slack vereist de functie Agents & AI Apps in een betaald abonnement. Een weigering op het moment van de aanroep (streaming_not_supported, feature_not_enabled, paid_only, …) wordt onthouden voor de hele levensduur van de verbinding en verschijnt als streamingAvailable: false. De taakkaarten van Slack liften mee op dezelfde aanroep, dus een Slack die de weergavemodus van de kaart weigert, behoudt zijn stream en verliest alleen de kaart, één keer, voor de hele levensduur van de verbinding.

Een stream moet ook de lezer noemen voor wie hij bedoeld is. Slack vereist zowel een ontvanger als diens team om er een te openen in een kanaal (een DM niet) en weigert anders met missing_recipient_user_id / missing_recipient_team_id — een weigering per aanroep, dus de brug blijft het proberen en ondertussen komt de beurt binnen als één geplaatst antwoord. De ontvanger is wie het vroeg, overgenomen uit het inkomende bericht (diens team, niet dat van de app, zodat een Slack Connect-kanaal correct wordt herleid). Voor een beurt die niemand vanuit de chat is begonnen — werk dat in Control Center is gestart op een al overbrugd gesprek — is dat het lid wiens koppeling de brug heeft gemaakt; heeft die sindsdien zijn chataccount ontkoppeld, dan wordt die beurt in zijn geheel geplaatst in plaats van gestreamd.

Eén gegroepeerde kaart per agentbeurt (task_display_mode: plan), met een regel per stap. Elke regel is een task_update van id + title + status, plus een eenmalige details wanneer de gedachte klaar is. details en output met hetzelfde id worden zonder scheidingsteken aan elkaar geplakt, dus details worden één keer verstuurd en output wordt nooit gestreamd. De titel van het plan is het verzoek; het antwoord is een aparte latere aanvulling met een markdown_text-fragment.

Onderdeel Inhoud
Titel van het plan Wat er gevraagd is
Voorbereidingsregel Working on it…, dan Cloning …, dan Starting the agent… — één regel, titel vervangen
Denkregel Thinking…, toegevoegd wanneer de agent begint te redeneren; de gedachte is de details van deze regel, één keer verstuurd wanneer het redeneren klaar is
Toolregels Eén per tool (Bash which swift), uniek id zodat ze naast elkaar staan
Status Loopt terwijl die regel draait; voltooid wanneer de volgende regel begint of de beurt eindigt; fout voor mislukt, onderbroken en de beurtlimiet
Oproep tot actie View in Control Center als afsluitende regel wanneer de beurt eindigt — afwezig terwijl het werk nog loopt en afwezig wanneer de server geen bereikbare openbare URL heeft

Wat een kaart nooit draagt: tooluitvoer, een argument buiten die allowlist, of wat dan ook dat uit de database is gelezen. De redeneertekst is alleen de details van de regel Thinking… (afgetopt op 256 tekens), niet de berichttekst. Een kaart die niets nieuws zou zeggen, wordt niet opnieuw verstuurd. Een vermelding opent de stream meteen, zodat de Slack-thread niet stil blijft; een beurt die daarna helemaal geen tekst opleverde, rondt diezelfde kaart af met een korte notitie in plaats van een tweede bericht te plaatsen. Een woordeloze beurt op een ruimte die nooit een stream heeft geopend (geen kaarten, of streamen al geweigerd) plaatst nog steeds één bericht met de voltooide kaart en de notitie.

Gestreamde en geplaatste kaarten zijn verschillende payloads:

Vorm Structuur Tekst
Gestreamd plan_update plus task_update-fragmenten met als sleutel id Gewone string, afgetopt op 256 tekens per fragment
Geplaatst Een plan-blok dat task_cards omvat, met als sleutel task_id Rijke tekst

Afhandeling van fouten:

Situatie Gedrag
De provider weigert de kaart bij een aanvulling De aanvulling wordt direct opnieuw geprobeerd zonder de kaart; tekst komt nog steeds live binnen en kaarten worden weggegooid voor de hele levensduur van de verbinding
De provider weigert ook de tekst De stream wordt gesloten en het antwoord wordt in zijn geheel geplaatst
De stream breekt af nadat een deel van het antwoord al binnen was De stream wordt gesloten en er wordt niets opnieuw geplaatst

Zie Chatbruggen voor waarom de payload zo wordt opgesplitst.

De kaart opent wanneer een vermelding wordt geaccepteerd, vóór het klonen en vóór het eerste token; de beurt die volgt neemt dezelfde kaart en stream over. De meldingen daarna komen van SpaceProvisioningChanged op de gebeurtenissenbus van het domein — dezelfde voortgang die de voorbereidingsbanner van de desktop van de ruimteregel leest.

Voorbereidingsstatus Melding
Vermelding geaccepteerd Working on it…
Stap repo Cloning <repo>…
Stap prCheckout Checking out the pull request in <repo>…
Stap script Running the setup script for <repo>…
Stap agent Setting up <agent>…
provisioning, nog geen stap Preparing the workspace…
ready Starting the agent…
Agent redeneert Thinking…
failed Workspace setup failed — details are in Control Center.
cancelled Workspace setup was stopped — details are in Control Center.

Voorbereidingsmeldingen vereisen alle vier de voorwaarden:

Voorwaarde
De provider geeft kaarten weer
Streamen is beschikbaar op deze verbinding
De ruimte heeft een chatverzoek onderweg
Er rapporteert daar al geen kaart een beurt

Het verzoek blijft onderweg tot een beurt begint of de voorbereiding mislukt. Een ready-gebeurtenis zonder al geopende kaart is een warme ruimte en blijft stil, en een voorbereiding waar niemand in de chat op wacht — een gesprek dat in de app is geopend, een achtergebleven gesprek dat bij het opstarten wordt hervat — levert helemaal geen kaart op.

Chatproducten accepteren in een link alleen http(s) en niets anders, dus een oproep tot actie kan het schema van de desktopapp niet rechtstreeks noemen.

Eigenschap Waarde
Route GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id>
Herkomst Afgeleid van de openbare URL van de server (ws → http, wss → https); loopback blijft behouden
Antwoord Een kleine HTML-pagina die niets cachet, naar control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> navigeert en een handmatige knop toont
Validatie Id’s moeten overeenkomen met [A-Za-z0-9_.-]{1,128}; al het andere is een 404
Toegang Openbaar en inert — de pagina leest geen database en is dus geen bestaansorakel; de app die hij opent, doet de autorisatie
Veld Limiet Opmerkingen
appName 1–35 tekens Standaard Control Center · <workspace name>
botDisplayName 1–80 tekens Standaard control-center
description ≤ 140 tekens
agentDescription ≤ 300 tekens Wordt boven een DM met de bot getoond
command ≤ 32 tekens Genormaliseerd: leidende slashes verwijderd, kleine letters, [^a-z0-9_-] verwijderd, leeg valt terug op cc
agentEnabled — Of de DM- en agentervaring wordt aangeboden

Een leeg vak betekent “laat het met rust”, niet “wis het”. De limieten zijn die van Slack, de strengste van de gemodelleerde producten.

Eigenschap Waarde
Lengte 6 tekens
Alfabet 23456789ABCDEFGHJKLMNPQRSTVWXYZ (geen 0/O, 1/I, U)
Levensduur 15 minuten
Gebruik Eén keer
Opslag In het geheugen — een herstart van de server maakt openstaande codes ongeldig
Bereik Eén werkruimte en één provider; een nieuwe code aanmaken maakt de vorige van het lid ongeldig

Het inwisselen wordt hoofdletterongevoelig gematcht. Codes die onbekend, verlopen, van een andere werkruimte of van de verkeerde provider zijn, falen allemaal bewust op dezelfde manier.

Methode Wire Hoe
Code code Het lid wisselde /cc link <code> in
E-mail email Het geverifieerde e-mailadres van de provider kwam overeen met een gebruiker die al lid van de werkruimte is

Alle zijn werkruimtegebonden. De server is stateloos, dus elke aanroep draagt zijn eigen workspace_id in args mee; de dispatcher weigert een werkruimtegebonden operatie die dat weglaat, voordat er een database opengaat. Inloggegevens gaan alleen naar binnen — statusuitlezingen geven ze nooit terug.

Operatie Soort Min. rol Actieklassen
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 voor jezelf, admin voor iemand anders —
chat.createApp mutate admin secretAccess, networkEgress
chat.setupLink read admin —
chat.botProfile read admin networkEgress
chat.updateBotProfile mutate admin networkEgress

Leesoperaties erven de guest-ondergrens van hun soort; mutaties zonder expliciete ondergrens vereisen member.

chat.setupLink stelt een URL voor het aanmaken van een provider-app samen uit het botprofiel. Het declareert geen actieklasse, omdat het geen geheim leest en niets aanroept — de browser van de gebruiker zelf doet de enige aanroep.

Eén reactieve query maakt het oppervlak compleet:

Query Soort Levert
chat.watchUserLinks watch De lijst, opnieuw gepusht telkens wanneer er een koppeling bijkomt of verdwijnt

Hij bestaat omdat het koppelen aan de andere kant gebeurt: een lid typt de code voor eenmalig gebruik in de bot, dus geen enkel antwoord van de client kan de uitkomst terugbrengen. De lijst “Gekoppelde accounts” in de instellingen en de geopende dialoog “Koppel je Slack-account” volgen hem beide, en de dialoog springt op een bevestiging op het moment dat de eigen koppeling van het lid binnenkomt.

chat.beginUserLink maakt altijd een code aan voor de eigen identiteit van de aanroeper. chat.updateBotProfile geeft alleen een remaining_step terug wanneer er nog een stap over is (meestal een herinstallatie); het ontbreken ervan betekent dat de bewerking meteen van kracht is.

chat.providers geeft één vermelding per aangeboden provider terug — de descriptor plus, als die er is, de verbindingsstatus. Alles wat de instellingen tonen, komt uit die payload, en daarom is een provider toevoegen een wijziging die alleen de server raakt.

Wat Waar
Inloggegevens van verbindingen <dataDir>/<workspaceId>/chat_credentials/<provider>.json
Halfvoltooide begeleide installatie <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json
Oude Slack-bestanden <dataDir>/<workspaceId>/slack_credentials.json en slack_app_setup.json, één keer gelezen en ter plaatse gemigreerd

Inloggegevens staan naast de database van de werkruimte, niet erin: het verwijderen van een werkruimte neemt de chattokens mee, en workspace.export of een back-up kopieert workspace.db — dus een werkruimte die je aan iemand anders geeft, bevat geen levende bottokens. Er wordt atomair weggeschreven (tijdelijk bestand plus rename) en de rechten gaan naar alleen de eigenaar.

Beide staan in de database van de werkruimte, die één samengevoegde v1-basislijn heeft — dus een uitlezing over werkruimtes heen compileert niet.

chat_space_links — één extern gesprek ↔ één Control Center-ruimte:

Kolom Opmerkingen
id UUID v4
workspace_id Werkruimtebereik
provider ChatProvider.wire
external_team_id Slack-team T…, leeg voor providers zonder dat concept
external_channel_id Slack-kanaal C…, groep G…, DM D…
external_thread_id Slack-threadanker, of null wanneer het hele gesprek het anker is (bot-DM’s)
cc_space_id REFERENCES spaces(id) ON DELETE CASCADE
created_by_user_id, created_at, last_activity_at

Uniek op (workspace_id, provider, external_channel_id, external_thread_id), met een partiële index die hetzelfde afdwingt voor null-thread-id’s (SQLite behandelt NULL’s als verschillend).

chat_user_links — één extern lid ↔ één Control Center-gebruiker:

Kolom Opmerkingen
id UUID v4
workspace_id Werkruimtebereik
provider ChatProvider.wire
external_team_id, external_user_id Slack-lid U…
user_id Control Center-gebruiker (globaal, dus geen foreign key)
method code of email
linked_at

Uniek op (workspace_id, provider, external_team_id, external_user_id) en op (workspace_id, provider, user_id) — attributie met één waarde in beide richtingen, terwijl één persoon bij meerdere providers gekoppeld kan zijn.

Een koppeling is identiteitsherleiding, geen toegangsverlening: het lidmaatschap wordt bij elk inkomend event opnieuw gecontroleerd, dus een koppeling naar een lid dat inmiddels is verwijderd, leidt tot een weigering.

Een overbrugde ruimte wordt aangemaakt met de SpaceKind van de provider (slack). Hij gedraagt zich als een gewone, mensgerichte ruimte — gemeld en ingedeeld zoals topic — en isExternalChat is de controle waar elk gedrag (spiegelen, badges, retentie) doorheen gaat.

Elk bericht dat de brug binnenbrengt, draagt een stempel metadata['chat']:

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

Het stempel is ook de echobewaking: de uitgaande spiegel slaat elk bericht met dat stempel over, en dat is wat voorkomt dat een inkomend chatbericht direct wordt teruggeplaatst.

Gedrag Details
Wat naar buiten gaat Alleen de tekst van de agent, plus de statusregel van de taakkaart. Redeneertekst en tooluitvoer blijven in Control Center
Streamritme Afgeknepen tot één flush per seconde terwijl een beurt loopt; de kaart en de tekst die hij toelicht, gaan mee in dezelfde aanroep
Menselijke berichten getypt in Control Center Gespiegeld naar de gekoppelde thread, met de weergavenaam van de auteur ervoor
Lege beurten Gemeld als een korte notitie (run mislukt, onderbroken, of gestopt op zijn beurtlimiet) in plaats van stilte
Deduplicatie Gesleuteld op het eigen event-id van de provider; de laatste 512 worden onthouden
Voordeur-agent Een nieuw overbrugd gesprek wekt de CEO-agent van de werkruimte, anders zijn eerste agent, anders niemand
Naamgeving van ruimtes #channel · <request> voor een Slack-kanaalthread, Slack DM · <request> voor een DM, titels afgetopt op 48 tekens
Foutisolatie Eén slecht event haalt de brug nooit neer; de verbinding blijft elk ander gesprek bedienen