Zum Inhalt springen

Chat-Brücke

Anbieter Drahtwert Transport Bereichsart
Slack slack Socket Mode (ausgehendes WSS) slack

Slack ist der einzige Anbieter. ChatProvider ist ein Enum mit einem Wert, und cc_server registriert ein Plugin. Schema, jede Suche und jeder eindeutige Index hängen an provider, deshalb kreuzen sich IDs nie über Anbieter, und ein weiterer ist eine Änderung nur am Server.

Eine App auf Anbieterseite pro Arbeitsbereich × Anbieter.

Der Befehlsname gilt pro App (cc als Vorgabe, also /cc). Ein Admin kann ihn umbenennen; die Brücke benutzt danach, womit sie zuletzt aufgerufen wurde.

Befehl Wirkung
/cc help (oder /cc ohne Verb) Listet, was der Bot kann
/cc link <code> Löst einen Einmalcode ein und verknüpft die Chat-Identität des Absenders
/cc ticket <title> Legt ein lokales Ticket an, gemeldet vom verknüpften Mitglied
/cc ticket <title> | <description> Dasselbe, alles nach dem ersten | ist die Beschreibung
Unbekanntes Verb Meldet das unbekannte Verb plus den Hilfetext

Wege zu einem Agenten:

Auslöser Verhalten
@bot … in einem Channel Öffnet einen Slack-Thread und einen passenden Bereich in Control Center; der Agent antwortet im Slack-Thread
Antwort in einem überbrückten Slack-Thread Führt denselben Bereich in Control Center weiter — keine Erwähnung nötig
Gewöhnliche Nachricht in einem Channel Wird ignoriert (nicht an den Bot gerichtet und nicht in einem überbrückten Slack-Thread)
Direktnachricht an den Bot Ein Bereich in Control Center pro DM-Unterhaltung; setzt einen Slack-Thread-Titel und den Status „is thinking…“, solange der Agent arbeitet

Der Deskriptor des Anbieters legt sie fest. Daraus entstehen der Verbindungsdialog und die Prüfung der Eingabe.

Feld-ID Bezeichnung Pflicht Präfix Wo es liegt
botToken Bot-Token Ja xoxb- OAuth & Permissions
appToken App-Token Ja xapp- Basic Information → App-Level Tokens, scope connections:write
configRefreshToken App-Konfigurationstoken Nein xoxe App configuration tokens auf api.slack.com/apps

configRefreshToken ist das Zugangsdatum für die App-Verwaltung: ohne es funktioniert die Verbindung, aber der Bot lässt sich aus Control Center weder anlegen noch bearbeiten. Es rotiert bei jeder Verwendung, und der rotierte Wert wird sofort gespeichert.

Werte werden an den Enden beschnitten, nicht deklarierte Schlüssel fallen weg, und ein falscher Präfix wird mit demselben Satz auf dem Client und dem Server abgelehnt. Pflichtfelder müssen bei jedem Verbinden gesetzt sein; ein leeres optionales Feld behält, was schon gespeichert war.

Scopes des Bot-Tokens:

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

Ereignisabonnements des Bots:

app_home_opened
app_mention
message.channels
message.groups
message.im

Strukturelle Einstellungen, die Control Center beim Anlegen oder Bearbeiten der App durchsetzt: Socket Mode an, keine Request URL, der Tab Messages aktiviert und beschreibbar, Bot always_online, die erforderlichen Scopes und Ereignisse in das eingefügt, was die App schon hat (nie abgezogen), und ein Slash-Befehl mit der Beschreibung File a ticket or link your Control Center account — das Kennzeichen, an dem sich der eigene Befehl von Control Center in einem exportierten Manifest erkennen lässt.

Zustand Drahtwert Bedeutung
Nicht verbunden disconnected Keine Zugangsdaten für diesen Arbeitsbereich × Anbieter gespeichert
Verbinden… connecting Zugangsdaten gespeichert, der Transport wählt oder wählt neu
Live connected Transport steht und empfängt Ereignisse
Verbindungsfehler error Gespeichert, aber fehlerhaft; der eigene Fehler des Anbieters steht daneben

Die Wiederverbindung ist exponentieller Backoff mit Jitter, gedeckelt bei 30 Sekunden. Eine vom Anbieter angestoßene Socket-Rotation verbindet sofort neu, ohne den Backoff hochzusetzen. Ein abgelehntes Zugangsdatum (invalid_auth, not_authed, token_revoked, account_inactive, token_expired, invalid_app_id) oder ein fehlender Scope ist endgültig: Die Brücke versucht es nicht weiter, bis neue Zugangsdaten da sind.

Die Brücke ist gegen die reichste Oberfläche geschrieben und fällt Fähigkeit um Fähigkeit zurück.

Fähigkeit Slack Wirkung, wenn sie fehlt
streaming ja Die ganze Antwort wird einmal gepostet, am Ende
streamingRequiresThread ja Ein Antwortziel ohne Thread-Anker postet ganze Antworten
ephemeralMessages ja Eine Antwort auf einen Slash-Befehl ohne verwendbares Antwort-Handle wird verworfen (Ablehnungen werden immer im Thread gepostet)
threadStatus ja Keine flüchtige Zeile „is thinking…“
threadTitle ja Der Chat-Thread behält seinen Standardtitel
slashCommands ja Keine Oberfläche /cc
taskCards ja Nur Text — keine Karte über der Antwort und kein Link „View in Control Center“
maxMessageLength 39.000 Längere Antworten werden abgeschnitten, mit einem Verweis auf Control Center
maxStreamChunkLength 3.800 Anhänge werden an einer Grenze zwischen Absatz, Zeile oder Leerzeichen getrennt

Gemeldetes Streaming ist kein zugesagtes: Das Streaming von Slack braucht die Funktion Agents & AI Apps in einem bezahlten Tarif. Eine Ablehnung zum Aufrufzeitpunkt (streaming_not_supported, feature_not_enabled, paid_only, …) gilt für die Lebensdauer der Verbindung und erscheint als streamingAvailable: false. Die Aufgabenkarten von Slack laufen über denselben Aufruf. Lehnt Slack den Anzeigemodus der Karte ab, bleibt der Stream, und verloren geht nur die Karte, einmal, für die Lebensdauer der Verbindung.

Ein Stream muss außerdem benennen, für wen er ist. Slack verlangt zum Öffnen in einem Channel sowohl einen Empfänger als auch dessen Team (eine Direktnachricht nicht) und lehnt sonst mit missing_recipient_user_id / missing_recipient_team_id ab — eine Ablehnung pro Aufruf. Die Brücke versucht es also weiter, und der Zug kommt inzwischen als eine gepostete Antwort an. Empfänger ist, wer gefragt hat, genommen aus der eingehenden Nachricht (sein Team, nicht das der App, damit ein Channel von Slack Connect richtig aufgelöst wird). Für einen Zug, den niemand aus dem Chat gestartet hat — Arbeit, die in Control Center auf einer schon überbrückten Unterhaltung angestoßen wurde — ist es das Mitglied, dessen Verknüpfung die Brücke angelegt hat; hat es sein Chat-Konto inzwischen gelöst, wird dieser Zug im Ganzen gepostet statt gestreamt.

Eine gruppierte Karte pro Agenten-Zug (task_display_mode: plan), mit einer Zeile pro Schritt. Jede Zeile ist ein task_update aus id + title + status, plus einmalige details, sobald der Gedanke fertig ist. details und output derselben ID werden ohne Trenner aneinandergehängt, deshalb gehen details einmal hinaus und output wird nie gestreamt. Der Titel des Plans ist die Anfrage; die Antwort ist ein späterer eigener Anhang mit einem Stück markdown_text.

Teil Inhalt
Plantitel Was gefragt wurde
Einrichtungszeile Working on it…, dann Cloning …, dann Starting the agent… — eine Zeile, der Titel wird ersetzt
Denkzeile Thinking…, hinzugefügt, wenn der Agent zu denken beginnt; der Gedanke sind die details dieser Zeile, einmal gesendet, wenn das Denken endet
Tool-Zeilen Eine pro Tool (Bash which swift), eindeutige ID, damit sie nebeneinander stehen
Status In Arbeit, solange diese Zeile läuft; abgeschlossen, wenn die nächste Zeile beginnt oder der Zug endet; Fehler bei Fehlschlag, Unterbrechung und Zuglimit
Handlungsaufforderung View in Control Center als abschließende Zeile, wenn der Zug endet — fehlt, solange die Arbeit noch läuft, und fehlt, wenn der Server keine erreichbare öffentliche URL hat

Was eine Karte nie trägt: Tool-Ausgabe, ein Argument außerhalb dieser Freigabeliste oder irgendetwas, das aus der Datenbank gelesen wurde. Der Denktext steht nur in den Details der Zeile Thinking… (gedeckelt bei 256 Zeichen), nicht im Nachrichtentext. Eine Karte, die nichts Neues sagen würde, wird nicht erneut gesendet. Eine Erwähnung öffnet den Stream sofort, damit der Slack-Thread nicht stumm bleibt; ein Zug, der danach gar keinen Text erzeugt hat, schließt dieselbe Karte mit einer kurzen Notiz ab, statt eine zweite Nachricht zu posten. Ein wortloser Zug in einem Bereich, der nie einen Stream geöffnet hat (keine Karten, oder Streaming schon abgelehnt), postet trotzdem eine Nachricht mit der fertigen Karte und der Notiz.

Gestreamte und gepostete Karten sind verschiedene Nutzlasten:

Form Gestalt Text
Gestreamt plan_update plus task_update-Stücke, Schlüssel id Einfache Zeichenkette, gedeckelt bei 256 Zeichen pro Stück
Gepostet Ein plan-Block um task_cards mit Schlüssel task_id Rich Text

Fehlerbehandlung:

Lage Verhalten
Der Anbieter lehnt die Karte bei einem Anhang ab Der Anhang wird sofort ohne sie wiederholt; der Text kommt weiter live an, und Karten entfallen für die Lebensdauer der Verbindung
Der Anbieter lehnt auch den Text ab Der Stream wird geschlossen und die Antwort im Ganzen gepostet
Der Stream bricht ab, nachdem ein Teil der Antwort angekommen ist Der Stream wird geschlossen und nichts erneut gepostet

Siehe Chat-Brücken, warum die Nutzlast so geteilt ist.

Die Karte geht auf, sobald eine Erwähnung angenommen wird, vor dem Klonen und vor dem ersten Token; der Zug danach übernimmt dieselbe Karte und denselben Stream. Die Erzählung danach kommt von SpaceProvisioningChanged auf dem Domain-Ereignisbus — derselbe Fortschritt, den das Bereitstellungsbanner des Desktops von der Zeile des Bereichs liest.

Bereitstellungszustand Erzählung
Erwähnung angenommen Working on it…
Schritt repo Cloning <repo>…
Schritt prCheckout Checking out the pull request in <repo>…
Schritt script Running the setup script for <repo>…
Schritt agent Setting up <agent>…
provisioning, noch kein Schritt Preparing the workspace…
ready Starting the agent…
Der Agent denkt Thinking…
failed Workspace setup failed — details are in Control Center.
cancelled Workspace setup was stopped — details are in Control Center.

Die Erzählung der Einrichtung braucht alle vier Bedingungen:

Bedingung
Der Anbieter zeichnet Karten
Streaming ist auf dieser Verbindung verfügbar
Der Bereich hat eine laufende Chat-Anfrage
Keine Karte meldet dort schon einen Zug

Die Anfrage bleibt offen, bis ein Zug beginnt oder die Einrichtung scheitert. Ein ready-Ereignis ohne schon offene Karte ist ein schon bereiter Bereich und bleibt still. Eine Bereitstellung, auf die im Chat niemand wartet — eine in der App geöffnete Unterhaltung oder eine hängen gebliebene, die beim Start weiterläuft — erzeugt gar keine Karte.

Chat-Produkte nehmen in einem Link http(s) an und sonst nichts, deshalb kann eine Handlungsaufforderung das Schema des Desktops nicht direkt nennen.

Eigenschaft Wert
Route GET /open/workspaces/<workspaceId>/(spaces|tickets)/<id>
Ursprung Abgeleitet von der öffentlichen URL des Servers (ws → http, wss → https); Loopback bleibt
Antwort Eine kleine HTML-Seite mit no-store, die zu control-center://workspaces/<workspaceId>/(spaces|tickets)/<id> weiterleitet und eine Schaltfläche zum Öffnen von Hand zeigt
Prüfung IDs müssen [A-Za-z0-9_.-]{1,128} entsprechen; alles andere ist ein 404
Zugriff Öffentlich und wirkungslos — sie liest keine Datenbank und ist damit kein Existenzorakel; die App, die sie öffnet, prüft die Autorisierung
Feld Grenze Anmerkungen
appName 1–35 Zeichen Vorgabe Control Center · <workspace name>
botDisplayName 1–80 Zeichen Vorgabe control-center
description ≤ 140 Zeichen
agentDescription ≤ 300 Zeichen Steht über einer DM mit dem Bot
command ≤ 32 Zeichen Normalisiert: führende Schrägstriche entfernt, kleingeschrieben, [^a-z0-9_-] entfernt, leer fällt auf cc zurück
agentEnabled — Ob der Bot die DM- und Agenten-Erfahrung anbietet

Ein leeres Feld heißt „stehen lassen“, nicht „leeren“. Die Grenzen sind die von Slack, die engsten der abgebildeten Produkte.

Eigenschaft Wert
Länge 6 Zeichen
Alphabet 23456789ABCDEFGHJKLMNPQRSTVWXYZ (kein 0/O, 1/I, U)
Lebensdauer 15 Minuten
Verwendungen Eine
Ablage Im Speicher — ein Neustart des Servers macht noch offene Codes ungültig
Geltungsbereich Ein Arbeitsbereich und ein Anbieter; ein neuer Code macht den vorherigen des Mitglieds ungültig

Eingelöst wird ohne Rücksicht auf Groß- und Kleinschreibung. Unbekannte, abgelaufene, einem fremden Arbeitsbereich gehörende und für den falschen Anbieter erzeugte Codes scheitern alle absichtlich auf dieselbe Weise.

Methode Drahtwert Wie
Code code Das Mitglied hat /cc link <code> eingelöst
E-Mail email Die verifizierte E-Mail des Anbieters passte zu einem Nutzer, der schon Mitglied des Arbeitsbereichs ist

Alle sind auf einen Arbeitsbereich bezogen. Der Server ist zustandslos, deshalb trägt jeder Aufruf seine eigene workspace_id in den Argumenten; der Dispatcher lehnt eine arbeitsbereichsbezogene Operation ab, die sie auslässt, bevor irgendeine Datenbank aufgeht. Zugangsdaten gehen nur hinein — Statusabfragen geben sie nie zurück.

Operation Art Mindestrolle Aktionsklassen
chat.providers Lesen guest —
chat.status Lesen guest —
chat.connect Ändern admin secretAccess, networkEgress
chat.disconnect Zerstörend admin secretAccess
chat.listUserLinks Lesen guest —
chat.beginUserLink Ändern member —
chat.unlinkUser Ändern member für dich selbst, admin für jede andere Person —
chat.createApp Ändern admin secretAccess, networkEgress
chat.setupLink Lesen admin —
chat.botProfile Lesen admin networkEgress
chat.updateBotProfile Ändern admin networkEgress

Leseoperationen erben die Untergrenze guest aus ihrer Art; Mutationen ohne ausdrückliche Untergrenze verlangen member.

chat.setupLink setzt aus dem Bot-Profil eine URL zum Anlegen der App beim Anbieter zusammen. Es deklariert keine Aktionsklasse, weil es kein Geheimnis liest und nichts anwählt — der eigene Browser des Nutzers macht den einzigen Aufruf.

Eine reaktive Abfrage schließt die Oberfläche ab:

Abfrage Art Liefert
chat.watchUserLinks Beobachten Die Liste, erneut geschoben, sobald eine Verknüpfung entsteht oder verschwindet

Es gibt sie, weil das Verknüpfen auf der anderen Seite geschieht: Ein Mitglied tippt den Einmalcode in den Bot, also kann keine Antwort des Clients das Ergebnis zurücktragen. Die Liste in den Einstellungen und der offene Dialog „Mein Slack-Konto verknüpfen“ folgen ihr beide, und der Dialog wechselt zur Bestätigung, sobald die eigene Verknüpfung des Mitglieds ankommt.

chat.beginUserLink erzeugt immer einen Code für die eigene Identität des Aufrufers. chat.updateBotProfile gibt ein remaining_step nur zurück, wenn noch ein Schritt offen ist (meist eine Neuinstallation); fehlt es, ist die Bearbeitung live.

chat.providers gibt einen Eintrag pro angebotenem Anbieter zurück — den Deskriptor und, wenn es einen gibt, den Verbindungsstatus. Alles, was die Einstellungen anzeigen, kommt aus dieser Nutzlast, und deshalb ist ein weiterer Anbieter eine Änderung nur am Server.

Was Wo
Verbindungs-Zugangsdaten <dataDir>/<workspaceId>/chat_credentials/<provider>.json
Halb fertige geführte Einrichtung <dataDir>/<workspaceId>/chat_credentials/<provider>_setup.json
Ältere Slack-Dateien <dataDir>/<workspaceId>/slack_credentials.json und slack_app_setup.json, einmal gelesen und an Ort und Stelle migriert

Die Zugangsdaten liegen neben der Datenbank des Arbeitsbereichs, nicht darin: Wird der Arbeitsbereich gelöscht, gehen seine Chat-Tokens mit, und workspace.export oder eine Sicherung kopiert workspace.db — ein Arbeitsbereich, der an jemand anderen geht, trägt also keine aktiven Bot-Tokens. Geschrieben wird atomar (temporäre Datei plus Umbenennen) und nur für den Eigentümer.

Beide liegen in der Datenbank des Arbeitsbereichs, die eine zusammengezogene v1-Baseline trägt — ein arbeitsbereichsübergreifendes Lesen kompiliert deshalb nicht.

chat_space_links — eine externe Unterhaltung ↔ ein Bereich in Control Center:

Spalte Anmerkungen
id UUID v4
workspace_id Geltungsbereich des Arbeitsbereichs
provider ChatProvider.wire
external_team_id Slack-Team T…, leer bei Anbietern ohne den Begriff
external_channel_id Slack-Channel C…, Gruppe G…, DM D…
external_thread_id Slack-Thread-Anker, oder null, wenn die ganze Unterhaltung der Anker ist (Bot-DMs)
cc_space_id REFERENCES spaces(id) ON DELETE CASCADE
created_by_user_id, created_at, last_activity_at

Eindeutig auf (workspace_id, provider, external_channel_id, external_thread_id), mit einem Teilindex, der dasselbe für Thread-IDs mit null erzwingt (SQLite behandelt NULLs als verschieden).

chat_user_links — ein externes Mitglied ↔ ein Nutzer in Control Center:

Spalte Anmerkungen
id UUID v4
workspace_id Geltungsbereich des Arbeitsbereichs
provider ChatProvider.wire
external_team_id, external_user_id Slack-Mitglied U…
user_id Nutzer in Control Center (global, deshalb kein Fremdschlüssel)
method code oder email
linked_at

Eindeutig auf (workspace_id, provider, external_team_id, external_user_id) und auf (workspace_id, provider, user_id) — eindeutige Zuordnung in beide Richtungen, während eine Person auf mehreren Anbietern verknüpft sein kann.

Eine Verknüpfung löst die Identität auf, sie ist keine Zugriffserteilung: Die Mitgliedschaft wird bei jedem eingehenden Ereignis neu geprüft, sodass eine Verknüpfung zu einem Mitglied, das inzwischen entfernt wurde, in einer Ablehnung endet.

Ein überbrückter Bereich entsteht mit der SpaceKind des Anbieters (slack). Er verhält sich wie ein gewöhnlicher, an Menschen gerichteter Bereich — benachrichtigt und einsortiert wie topic — und isExternalChat ist die Prüfung, durch die jedes Verhalten geht (Spiegeln, Badges, Aufbewahrung).

Jede Nachricht, die die Brücke hereinholt, trägt den Stempel metadata['chat']:

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

Der Stempel ist auch der Echo-Schutz: Der ausgehende Spiegel lässt jede Nachricht aus, die ihn trägt, und genau das verhindert, dass eine eingehende Chat-Nachricht sofort wieder hinausgepostet wird.

Verhalten Detail
Was hinausgeht Nur der Text des Agenten, plus die Statuszeile der Aufgabenkarte. Denktext und Tool-Ausgabe bleiben in Control Center
Streaming-Takt Gedrosselt auf einen Flush pro Sekunde, solange ein Zug läuft; die Karte und der Text, den sie erklärt, reisen im selben Aufruf
Von Menschen in Control Center getippte Nachrichten Gespiegelt in den verknüpften Thread, davor der Anzeigename des Autors
Leere Züge Gemeldet als kurze Notiz (Lauf fehlgeschlagen, unterbrochen oder am Zuglimit gestoppt), statt zu schweigen
Deduplizierung Schlüssel ist die eigene Ereignis-ID des Anbieters; die letzten 512 bleiben gemerkt
Eingangsagent Eine neue überbrückte Unterhaltung weckt den CEO-Agenten des Arbeitsbereichs, sonst seinen ersten Agenten, sonst niemanden
Benennung des Bereichs #channel · <request> für einen Slack-Channel-Thread, Slack DM · <request> für eine DM, Titel gedeckelt bei 48 Zeichen
Fehlerisolation Ein schlechtes Ereignis legt die Brücke nie lahm; die Verbindung bedient jede andere Unterhaltung weiter