Chatbroer
En chatbro gjør et eksternt chatprodukt — Slack i dag — til en førsteklasses vei inn i et Control Center-arbeidsområde. @-nevn boten, og en agent våkner; svar i Slack-tråden, og samtalen fortsetter; skriv en skråstrekkommando, og en sak opprettes. Under ligger de samme områdene, transkriptene, sakene og tilskrivningen som skrivebordsappen bruker. Chat er en flate i arbeidsområdet, ikke en kopi av det.
Slack er den eneste leverandøren som finnes. Det finnes ingen Discord-, Telegram-, Teams- eller Matrix-implementasjon noe sted i produktet. Funksjonen er bevisst skrevet leverandørblind — å legge til et nytt produkt er én enumverdi, én adapter og én plugin — men i dag betyr «chat» Slack.
To flater dekker det, og de tilhører ulike personer. Å koble et arbeidsområde til Slack og tilpasse boten er admin-handlinger under Innstillinger → Arbeidsområde → Generelt. Å knytte din egen chatkonto til Control Center-identiteten din er en medlems-handling under Innstillinger → Arbeidsområde → Profil og identitet.
Tre beslutninger former hele funksjonen: hvilken retning tilkoblingen går i, hva som autoriserer en melding som kommer utenfra og hvor den produktspesifikke kunnskapen får bo.
Utgående av design
Seksjon med tittelen «Utgående av design»Broen lytter aldri etter leverandøren. Slack nås over Socket Mode: serveren ber Slack om en socket-URL og ringer ut, og mottar deretter omtaler, direktemeldinger og skråstrekkommandoer over den tilkoblingen.
Konsekvensen er selve poenget. En bærbar maskin bak NAT, en hjemmeserver uten domene, en maskin som flytter seg mellom nettverk — alle kan kjøre broen med ingen innkommende port, ingen tunnel, ingen TLS-sertifikat og ingen offentlig URL å rotere når den endres. Det finnes ikke noe webhook-endepunkt å sikre, fordi det ikke finnes noe webhook-endepunkt. Når Control Center setter sammen appdefinisjonen, tvinger den gjennom Socket Mode og fjerner enhver request-URL, fordi en app som er konfigurert for begge, ville være en app som bare halvveis virker.
Prisen er en tilkobling som må holdes i live, og det er serverens jobb: gjenoppkobling skjer med eksponentiell backoff og jitter begrenset til 30 sekunder, en leverandørinitiert socketrotasjon kobler til på nytt uten å trappe opp den backoffen, og en avvist legitimasjon er terminal — å prøve et tilbakekalt token på nytt i det uendelige ville bare gi en hastighetsbegrensning, så broen stopper og venter på nye tokens mens den rapporterer leverandørens egen feil ordrett i innstillingene.
Et arbeidsområde uten chat-app koster ingenting i det hele tatt: ingen socket, ingen timere, ingen trafikk. Og det hele starter etter serverens klarmelding, fordi en chat-app som er treg til å svare, aldri må forsinke oppstarten.
Medlemskap autoriserer, ikke besittelse av token
Seksjon med tittelen «Medlemskap autoriserer, ikke besittelse av token»Den som installerte appen, er ikke den som sender meldingen. Derfor løses hver innkommende hendelse opp til en Principal før noe skjer, og den feiler lukket med en forklaring:
- En chatkonto som ikke er knyttet, får beskjed om hvordan den knyttes, i Slack-tråden den skrev i.
- En knyttet konto der brukeren ikke er medlem av dette arbeidsområdet, får beskjed om å be en admin om en invitasjon.
- Et medlem med en skrivebeskyttet rolle (
viewer,guest) får beskjed om at rollen ikke kan starte arbeid.
Ingen av disse går videre. En chatmelding tilskrives aldri noen som ikke sendte den — og det er dette som holder revisjonssporet, hastighetsbegrensningene per principal og saksrapportørene ærlige. Se Flerbruker — identitet, medlemskap og tilstedeværelse for medlemskapsmodellen dette lener seg på.
Knytning er identitetsoppslag, ikke en tilgangstildeling. Medlemskapet sjekkes på nytt for hver hendelse, så en knytning som tilhører noen som siden er fjernet, ender i en avvisning i stedet for i de gamle privilegiene deres. To veier etablerer en knytning: en kortlivet engangskode medlemmet skriver inn i chatten, eller en automatisk match på leverandørens verifiserte e-post — og e-postveien krever fortsatt at den matchede brukeren allerede er medlem, så en fremmed hvis adresse tilfeldigvis finnes på serveren, knyttes til ingenting.
Kodene holdes bevisst i minnet, med en levetid på 15 minutter. Å lagre én ville skape en varig rad formet som en legitimasjon for noe som er ment å fordampe, og at en serveromstart ugyldiggjør hver utestående kode, er riktig oppførsel, ikke en regresjon — medlemmet knytter kontoen på nytt.
En avvisning besvares i Slack-tråden meldingen åpnet, som et ekte svar. Et svar i tråden merker meldingen det svarer på, når den ene personen som trenger det, og holder samtidig «du er ikke knyttet til» utenfor selve kanalen. Å legge ut en melding er også den ene kapabiliteten alle leverandører har, så forklaringen avhenger aldri av en funksjon appen kanskje ikke har.
Proveniens og ekko-problemet
Seksjon med tittelen «Proveniens og ekko-problemet»En bro som speiler begge veier, vil gå i løkke med mindre noe skiller de to
retningene. Hver melding broen tar inn, stemples med metadata['chat'] —
leverandør, team, samtale, tråd, melding og avsender. Den utgående speilingen
hopper over enhver melding som bærer det stempelet.
Den ene regelen er det som gjør toveis speiling trygt: en melding et menneske skriver i Control Center, videresendes ut til den tilkoblede tråden (med forfatterens navn som prefiks), mens en melding som kom fra chatten, stopper der den kom inn.
Det som går ut, er bevisst smalt. Agentens svar videresendes som vanlig
chattekst. En kort tanke kan dukke opp på Thinking…-raden i kortet.
Verktøyutdata er en vegg av differ og blir værende i Control Center, som også er
dit et avkortet svar peker.
Et kort rapporterer arbeidet; teksten er fortsatt meldingen
Seksjon med tittelen «Et kort rapporterer arbeidet; teksten er fortsatt meldingen»Der leverandøren rendrer dem, bærer en tur ett oppgavekort, redigert på stedet så lenge turen varer og plassert over svaret det forklarer.
Kortet er én gruppert plan, ikke et most sammen avsnitt. Slack setter sammen
details og output på samme oppgave, så en rads detaljer sendes én gang
(tanken på Thinking…), og output strømmes aldri. Oppsett er én rad der
tittelen byttes ut (Working on it…, Cloning …). Resonnering legger til en
Thinking…-rad. Hvert verktøy er sin egen rad (Read lib/main.dart). Svaret er
vanlig tekst under kortet — Slacks planvisning leser det fra en senere
markdown-innføyelse, ikke fra en blokk blandet inn i oppgavene. Ingenting annet:
ingen verktøyutdata, og bare et verktøyargument fra en kort tillatelsesliste,
fordi argumentene til et ukjent verktøy kan være en hel filkropp, og en rad er
én linje.
Kortet dekker også ventingen før turen. En første omtale oppretter samtalen, og
å opprette den kloner hvert repo agenten skal jobbe i — noe som på et virkelig
repo er minutter der agenten ennå ikke er bedt om noe, og tråden ser ut som om
boten ignorerte forespørselen. Derfor åpnes kortet i det øyeblikket omtalen
godtas (Working on it…), og deretter oppdateres den oppsettraden med de samme
ordene skrivebordet viser — Cloning acme/widgets…, Setting up Reviewer…, Starting the agent… — så en Thinking…-rad så snart agenten
begynner å resonnere, og deretter én rad per verktøy. Ett kort, én melding, fra
«jeg hørte deg» og helt fram til svaret som vanlig tekst under. Oppsett
rapporteres bare der noen i chatten venter på det: en samtale som klargjøres i
appen, eller en strandet en som gjenopptas ved serveroppstart, provisjoneres helt
uten noe kort.
Det bærer det ene chatten ikke kan gi leseren: en View in Control Center-lenke
til samtalen bak svaret. Chatprodukter godtar http(s) i en lenke og ingenting
annet, så knappen peker på en liten side denne serveren betjener, som sender
nettleseren videre til skrivebordsappens control-center://-lenke. Den siden
leser ingenting og løser ingenting opp — id-ene i URL-en dens ekkoes inn i en dyp
lenke og ingen andre steder — og en server uten en tilgjengelig offentlig URL
produserer rett og slett et kort uten knapp i stedet for et ødelagt et.
Kort er en kapabilitet som alle andre. En leverandør uten dem oppfører seg
nøyaktig som før, bare tekst, og en leverandør som avviser ett på
kalltidspunktet, beholder strømmen sin: svaret går alltid foran kortet som
beskriver det. Når strømming er helt av, følger det ferdige kortet med den ene
meldingen som legges ut i stedet. En sak som opprettes, får samme behandling —
bekreftelsen på /cc ticket er et fullført kort, hvis eneste grunn til å finnes
er lenken til saken som nettopp ble opprettet.
Kapabiliteter forhandles, og huskes deretter
Seksjon med tittelen «Kapabiliteter forhandles, og huskes deretter»Chatprodukter er ulike i hva de kan rendre, så broen er skrevet mot den rikeste flaten — et svar som vokser live inne i en tråd, en forbigående «is thinking…»-status, en tråd med tittel, et flyktig svar på en skråstrekkommando — og nedgraderer én kapabilitet om gangen. En leverandør uten noe av dette virker fortsatt; svarene kommer bare som én melding.
Å annonsere en kapabilitet er ikke å love den. Slacks strømmende svar krever en funksjon i en betalt plan, og Slack avviser først på kalltidspunktet. En avvisning som er merket permanent, huskes for hele tilkoblingens levetid: broen slutter å spørre og legger ut hele svar fra da av, og innstillingene sier det rett ut, fordi «svarene mine sluttet å strømme» skal kunne besvares uten å lese en logg. Oppgavekort droppes på samme måte hvis Slack avviser ett.
Hver grense tilhører leverandøren og ikke reléet: taket for meldinger, taket for strømmeblokker og hvorvidt en strøm trenger et trådanpunkt, erklæres alle av adapteren, og et langt svar deles ved et avsnitt, en linje eller en ordgrense i stedet for midt i et ord.
Én bro, én adapter per produkt
Seksjon med tittelen «Én bro, én adapter per produkt»Alt produktformet bor bak en port. Den generiske kjernen vet om markdown,
chatsamtaler, chattråder og medlemmer; den vet ingenting om Slack-konvolutter,
mrkdwn eller gateway-intents. Tre deler får det til å virke:
- Adapteren normaliserer ett produkt: transporten, API-kallene, tekstkodeken
og konvolutt-særegenhetene. Slacks er lærerike — en kanal-omtale kommer inn to
ganger (som
app_mentionog sommessage), og de fleste meldingsundertyper er staffasje snarere enn nye forespørsler. Begge løses i adapteren, så kjernen aldri får vite at Slack leverer dobbelt. - Deskriptoren er leverandørens offentlige form: hvilken legitimasjon den trenger, hvilke formater den har, hvilke URL-er som hjelper en bruker å finne den, hva den kan gjøre, og hvorvidt appen kan opprettes eller redigeres herfra. Innstillingskortet, kobledialogen og argumentvalideringen genereres alle fra den — klienten sender tilbake en leverandør pluss et kart nøklet på felt-id.
- Pluginen er sammensetningen på serversiden: hvordan legitimasjon blir en verifisert tilkobling, hvordan en adapter bygges, og hvordan appen på leverandørsiden administreres.
Så å legge til et produkt er én enumverdi, én adapter og én plugin. Ingen ny RPC-operasjon, ingen skjemaendring, ingen klientendring — og klienten holder seg ærlig, fordi et felt den aldri har hørt om, fortsatt rendres med etiketten serveren oppgav.
To detaljer hindrer abstraksjonen i å lekke oppover. App-administrasjonsstegene en leverandør ikke har API for (generer et token, installer appen, installer på nytt etter en tillatelsesendring), returneres som navngitte steg med sine egne lenker, så skjermen for å fullføre oppsettet viser dem uten å vite hvilken leverandør det er. Og hver instruksjon broen skriver, navngir leverandøren og kommandoen appen faktisk bruker, så en omdøpt kommando eller en ny leverandør leses riktig uten noen ny streng.
Hvor legitimasjonen bor
Seksjon med tittelen «Hvor legitimasjonen bor»Et arbeidsområdes chattokens ligger i arbeidsområdets egen katalog, ved siden
av databasen — som ren JSON på
<dataDir>/<workspaceId>/chat_credentials/<provider>.json, ikke i databasen og
bevisst ikke i operativsystemets nøkkelring. To konsekvenser er lette å miste av
syne senere: å slette et arbeidsområde fjerner katalogen, og tokenene blir med
den, og å eksportere eller sikkerhetskopiere et arbeidsområde kopierer
workspace.db, så et arbeidsområde som gis videre til noen andre, ikke bærer
levende bottokens inni seg. Filen arver serververtens tillitsgrense i
filsystemet — alt med lesetilgang til datakatalogen kan lese et bottoken, som er
den samme grensen databasene ligger bak.
Én fil per leverandør, skrevet atomisk og kun for eieren: en tilbakekalt app slettes ved å fjerne én fil, og en ødelagt fil koster én leverandør i stedet for alle. Legitimasjon går inn via koblingsoperasjonen og kommer aldri ut igjen — statuslesinger svarer bare med tilkoblingsmetadata.
Hva som bevisst ikke tilbys
Seksjon med tittelen «Hva som bevisst ikke tilbys»- Strukturelle appinnstillinger. Transporten, scope-ene og hendelsesabonnementene tvinges gjennom, ikke eksponeres. En app som kan konfigureres til stillhet fra en innstillingsskjerm, er en supporthenvendelse, ikke en funksjon.
- Samtale pluss skråstrekkommandoer. Slack-flaten er meldinger, trådsvar og skråstrekkommandoer.
- En andre sannhetskilde. Chat er ikke et sted der arbeid lagres. Slack-tråden er et område, svaret er et transkript, saken er en sak — alle sammen i arbeidsområdet, tilgjengelige fra hver klient.
- Flerbruker — identitet, medlemskap og tilstedeværelse: partene og rollene broen løser opp gjennom
- Arbeidsområder og isolasjon: hvorfor knytninger og legitimasjon er per arbeidsområde
- Agentmodellen: hva som våkner når en omtale kommer inn
- Saker og delegering: hva
/cc ticketoppretter - Arkitektur: hvor broen sitter mellom server og klienter
- Koble Slack til arbeidsområdet ditt
- Sett opp Slack-integrasjon
- Koble til Slack-kontoen din
- Tilpass chatboten
- PR-samtaler: de samme tre beslutningene, tatt for en kodevert