機能連携

Slack

Slack 連携で今できることと、まだないこと

セッションが人の判断(ツール権限、質問への回答、push 承認)を待つと、そのスレッドにカードが投稿され、Slack でそのまま処理できる。承認と回答を参照。

今あるものAPI誰が
自分の Slack アカウントを Web アカウントに連携POST /v1/linked-accounts/slack/start, …/callback, GET /v1/linked-accounts, DELETE /v1/linked-accounts/{id}ログインした本人
エージェントに Slack アプリ(bot)を接続POST·GET /v1/agents/{id}/identities, DELETE …/identities/{identity_id}owner·admin·そのエージェントの管理者
guest のエージェント利用を許可POST /v1/workspaces/{id}/slack-guestowner·admin

三つとも Web ログインの cookie でのみ呼び出せる。API キーでは 403 になる。以下の例は作成の WEB ヘッダーと WS を使う。

guest の利用設定

Slack guest(マルチチャンネル·シングルチャンネル guest)がエージェントを使えるかどうかを決める。デフォルトはオフである。

WS=$(curl -sS "$API/v1/auth/me" "${WEB[@]}" | jq -r .workspace.id)
curl -sS "$API/v1/workspaces/$WS/slack-guest" "${WEB[@]}" \
  -d '{"slack_guest_enabled": true}' | jq .
{
  "slack_guest_enabled": true
}

オンにしても、guest は自分の Slack アカウントを連携しないと使えない。Slack Connect で参加した他社のユーザーは、この設定に関係なく使えない。

Slack アカウントの連携

Sign in with Slack(OpenID Connect)で連携する。運用者がコンソールの公開アドレス CONSOLE_ORIGIN を設定し、ワークスペース管理者が設定 > ワークスペース > 連携 > Slack に App Configuration Token を入れてアカウント連携用アプリを作れるようにし、そのワークスペースに確認済みの Slack インストールがある必要がある。ローカルスタックには CONSOLE_ORIGIN がないため、開始リクエストは 503 になる。

curl -sS -o /dev/null -w '%{http_code}\n' -X POST "$API/v1/linked-accounts/slack/start" "${WEB[@]}"
503

設定済みのインストールでは、開始リクエストが authorize_url を返す。その URL の Slack の認可画面で同意すると、Web の連携画面が code と state を callback に渡し、連携が完了する。連携の状態は active·stale·revoked である。

連携の応答と GET /v1/linked-accounts の各項目には、連携時に Slack が返した名前も入る。external_user_name は Slack の表示名(id_token の name)、external_tenant_name は Slack ワークスペース名(https://slack.com/team_name)である。もう一度連携すると新しい名前に置き換わる。名前を保存する前に作った連携や、Slack が名前を返さなかった連携は null で、Web 画面はそのとき ID を表示する。Slack のトークンは保存しない。そのため provider トークンの有効期限である expires_at は、Slack 連携では常に null である。開始応答の expires_at は連携リクエストの有効期限で、別の値である。

レスポンス意味
409 SLACK_NOT_INSTALLEDこのワークスペースに確認済みの Slack インストールがない
409 LINK_TEAM_MISMATCHインストールされていない Slack チームのアカウントである
409 LINK_TAKENその Slack アカウントは別の人に連携されている
409 LINK_EXISTS同じチームに自分の連携がすでにある

通知の言語

プラットフォームが Slack に書く案内文(拒否の案内、失敗・回答待ちの案内、Webで開く リンク、Web・API から送った指示の (Webコンソール) 表示)は、ユーザーが Web コンソールの 言語・テーマ で選んだ言語(한국어・日本語・English)に従います。コンソールは選んだ言語をサーバーに保存します(PATCH /v1/auth/me)。保存した言語がないままコンソールが英語以外の言語で開いた場合(ブラウザーの言語など)は、その言語を保存してコンソールと Slack の言語をそろえます。

  • 本人にだけ見える拒否の案内は、その Slack アカウントに連携したユーザーの言語で書きます。
  • スレッドに投稿する案内と指示の表示は、セッションを開始したユーザーの言語で書きます。スレッドは複数の人が見るため、1つの言語にそろえます。
  • 言語を保存したことがないユーザーと、連携していない Slack アカウントには英語で届きます。

モデルの回答とユーザーが入力した内容は翻訳しません。デプロイの歓迎メッセージは受け取る人が決まらないため、韓国語で送ります。

承認と回答

Slack のスレッドから始めたセッションが人を待つと、待っている要求ごとにそのスレッドへカードが 1 枚投稿される。カードはセッションを始めた人をメンションし、Web で同じ要求を開くリンクと有効期限を示す。カードは投稿するときにセッションを始めた人の言語(通知の言語)で書き、処理したあともその言語のままにする。

ツール権限のカードは、何をしようとしているかを 1 行に要約して示す。たとえば コマンド実行: bun test、ファイル編集: src/app.ts、PR作成: タイトル (ブランチ) のようになる。ファイルを書いたり編集したりするツールは、要約の下に書き込む内容の最初の 3 行も示す。モデルが渡した入力の原文は 入力の原文 の下に折りたたまれ、開いたときだけ見える。原文はフィールドごとに 名前: 値 で示し、値の中の改行は \n ではなく実際の改行で示す。長い原文は切り詰められるため、全体は Webで開く で見る。push 承認のカードもリポジトリ・ブランチ・コミットを先に示し、残りは 詳細 の下に折りたたむ。モデルが書いた文はカードのどこでもメンションやリンクにならない。

要求カードでできること
ツール権限許可・拒否。エンジンが繰り返し確認するルールを提案した場合は このセッション中は許可 が加わる。ルールはカードと確認ダイアログに表示される。リダイレクト・パイプ・複合コマンドなど、提案されたルールがその呼び出しに合わない可能性がある Bash 呼び出しにはボタンを出さない。押すと、このセッションが終わるまでそのルールに合う呼び出しを確認しなくなる。処理済みのカードは セッション中は許可 になる。push 承認とサーバーの許可リストはこのボタンでは変わらない
質問質問が 1 つで選択肢が 5 個以下なら選択肢ボタン、自由入力できるなら 直接入力。それ以外は 回答する で開くフォームで答える。質問が 10 個を超えるか 1 つの質問の選択肢が 100 個を超えると Slack のフォームに収まらないため、カードは「このリクエストはWebで回答してください」と Webで開く ボタンだけを示す
push 承認リポジトリ・ブランチ・コミットを確認して 承認(もう一度確認する)・却下

押した人は Slack アカウントが Web アカウントに連携されていて(Slack アカウントの連携)、Web でそのセッションの要求に答えられる人でなければならない。Web と同じ規則で判断する。連携していないアカウントには連携ページのリンクを、権限のない人には拒否の案内をその人にだけ表示し、何も処理しない。

処理するとカードからボタンが消え、結果が残る。Slack で押しても Web で答えても同じである。Web で答えたとき、要求が期限切れになったとき、セッションが終わって要求が閉じたときは、control-host 内の action-broker が少しあとに Slack のカードを同じ形に直す。1 行目は結果と処理した人で、どこで処理したかも示す。Slack で処理したなら 許可済み · @名前、Web で処理したなら Webで許可済み · @名前 になる。処理した人の Slack アカウントが連携されていなければ、名前なしで結果だけを示す。ツール権限と push のカードは、この結果に要求の要約と Web のリンクを付けた 1 行になる。質問のカードは質問の全文をそのまま残し、その下に選んだ答えを <選択肢>を選択(Web で答えたなら Webで<選択肢>を選択)で示す。直接入力した文は内容を出さず 直接入力 とだけ表示する。スレッドは複数の人が見るためである。期限切れやセッション終了で閉じた要求は誰もメンションしない。質問のカードは質問を残したまま、期限切れなら 期限切れ、セッションが終わって閉じたなら もう回答できません を示す。Slack がカードの更新を断ったら少しあとにやり直し、メッセージが削除されたかチャンネルがアーカイブされていればそのままにする。カードは押されたメッセージではなく、保存された要求から描き直す。Web や他の人がすでに処理した要求を押しても再び処理せず、カードを現在の結果に更新する。Slack で push を承認・却下すると、その結果をセッションに伝える指示が続き、セッションが PR を開くか却下を伝える。承認したときにセッションのターンが動いていれば、その指示はそのターンが終わるまで待ち、そのターンがすでに PR を開いていれば入れない。そのブランチに PR がすでにある場合も入れない。そのため同じ PR を二度開いたり、同じ返事を二度したりしない。

ボタンとフォームはエージェントアプリの Interactivity アドレス /v1/slack/interactions/{agent_id} に届く。イベントと同じく、そのアプリの signing secret による署名と 300 秒以内の時刻を確認する。インストールの finalize が Event Subscriptions のアドレスと一緒に manifest に入れる。この機能より前に作ったアプリは 再確認(POST /v1/agents/{id}/slack/app/events/recheck)で manifest を送り直すと登録される。

元の Slack メッセージへのリンク

Slack のメンション·DM·スレッドの返信で始めた、または送った指示は、その Slack メッセージの場所(チーム·チャンネル·ts、返信なら thread_ts)と Slack が返したリンクを一緒に残す。リンクは chat.getPermalink で受け取り、この呼び出しに追加の scope は要らない。Web のセッション画面は、ヘッダーの始めた人·経路の横と、会話の Slack からの指示の吹き出しに Slack で表示 リンクを出し、押すと新しいタブで開く。API ではセッションの slack_permalink(セッションを始めたメッセージ)と turn の slack_permalink(その指示を送ったメッセージ)で読む。

プライベートチャンネルと DM もリンクだけを残し、メッセージ本文はそれ以上保存しない(指示として受け取った文はこれまでどおり turn の message にある)。リンクは Slack でそのメッセージを読める人にだけ開く。リンクを残す前に作ったセッション·指示と、Slack がリンクを返さなかった指示は null で、画面にリンクは出ない。

Slack インストールの登録

運用者がターミナルで使う経路である。Slack ワークスペースにインストール済みの Slack アプリがあれば、そのアプリの bot token でインストールを登録する。コンソールで Slack アプリを自動で作成・インストールする流れは次のバージョンで入る。CLI が Slack の auth.test·bots.info でチーム、bot ユーザー、アプリを確認し、確認済み(verified)のインストールを残す。アプリには bot scope users:read が必要である。token は環境変数 SLACK_BOT_TOKEN でのみ渡す。CLI は token を引数で受け取らず、出力もしない。

read -rs SLACK_BOT_TOKEN && export SLACK_BOT_TOKEN
bun run slack-install register --workspace "$WS" --app-id A0123ABCD
unset SLACK_BOT_TOKEN
{"outcome":"verified","registration":"registered","installation_id":"…","workspace_id":"…","app_id":"A0123ABCD","team_id":"T0123ABCD","bot_user_id":"U0123ABCD"}

同じコマンドをもう一度実行すると、同じインストールを確認し直す(registration: existing)。失敗すると stderr に 1 行を書き、次の終了コードで終わる。

終了コード意味
0確認済みのインストールがある
2引数または環境変数が正しくない
3Slack が token を拒否した、または Slack に届かない
4そのアプリとチームは別のワークスペースのものである(workspace_conflict)。そこで解除されていても、そのワークスペースだけが再登録できる
5登録中にインストールが解除された(revoked)。もう一度実行すると再び開く
6ワークスペースが存在しない(workspace_not_found)
7token が --app-id とは別のアプリのものである(app_mismatch)
1その他のエラー

エージェントの Slack アプリ

アプリ設定の読み取り

owner・admin を含むエージェント管理者が Web セッションで呼び出す。GET /v1/agents/{id}/slack/app は event_url と events を返す。イベント URL は、サーバーがコンソールの公開アドレス(CONSOLE_ORIGIN)から計算した https アドレスに /v1/slack/events/{agent_id} を付けた絶対 URL である。イベントの入口をコンソールと別にしているサーバーは、その入口のアドレス(SLACK_EVENTS_PUBLIC_URL)を使う。計算できなければ null になる。events.state は pending・verified・failed である。インストール finalize が manifest にこのアドレスを Request URL として入れると pending になり、events.url に送ったアドレスが残る(送る前は null)。すでに verified のアプリに同じアドレスをもう一度入れても verified のままで、アドレスが変わると再び pending になる。そのアプリの signing secret で署名されたリクエスト(url_verification またはイベント)が初めて届くと verified になる。url_verification には署名と時刻を確認したうえで challenge の値だけを返す。manifest 登録に失敗すると failed になり、秘密値を含まない error コードを返す。checked_at は状態が最後に変わった時刻(まだなければ null)である。

finalize 後も pending なら、Slack はまだこのアドレスを確認できていない。公開 DNS がまだアドレスを解決できないこともあれば、Slack が manifest で入れた URL を確認していないこともある。POST /v1/agents/{id}/slack/app/events/recheck(コンソールの Slack タブ Slack アプリに入れるアドレス の 再確認)は manifest を Request URL とともにもう一度送る。それでも pending なら、Slack アプリ設定の Event Subscriptions 画面で Request URL の Verify(または Retry)を押す。コンソールの Slack タブは、アドレスを送った後 pending の間、同じ案内とその画面へのリンクを表示する。ローカルスタックで scripts/slack-funnel.sh on でアドレスを公開した場合は、最後の行の公開 DNS チェック(dns.google・cloudflare-dns.com)の警告が消えるまで待ってから Slack アプリを作成する。

GET /v1/agents/{id}/slack/app/manifest は現在のエージェントとサーバー設定から生成した manifest_yaml を返す。秘密値は含まない。

GET /v1/agents/{id}/slack-channels?ids=C1,C2 は最大 50 個の ID の名前を返す。各 item は id・name・state を持ち、Slack が存在しないかアクセスできないと答えた ID だけ名前と状態が unknown になる。通信失敗・上位サーバーのエラーはキャッシュせずリクエスト全体が 503 になり、失効したか無効な bot token は 409 SLACK_NOT_INSTALLED になる。cursor と全件走査はない。アプリ・チャンネルごとに 60 秒キャッシュし、Slack 429 は Retry-After 付きの 503 になる。

GET・PUT /v1/agents/{id}/slack/app/settings は { "mention_only": true } を読み書きする。true なら @メンションされたときだけ、false ならメンションがなくてもチャンネルへの投稿に応答する。どちらの値でも、エージェントがすでに会話中のスレッド(セッションにつながったスレッド)の返信は、メンションなしでそのセッションに続く。接続済みのチャンネルを含め、使用中のすべてのチャンネルに適用され、DM には適用されない。初期値は true で、アプリ作成前にも保存できる。アプリ全体のオン・オフは提供しない。

binding の mode は off(このチャンネルでは使わない)・on(使用中)である。mention_only が false なら、使用中のチャンネルで人がメンションなしでチャンネルに直接書いた投稿にもセッションを開く。エージェントが参加していないスレッドの返信には応答しない。

エージェントごとに自分の Slack アプリを持つ。接続には、アプリ id、チーム id、bot token が入った認証情報への参照の名前(credential_ref、値ではない)を送る。

curl -sS "$API/v1/agents/$AID/identities" "${WEB[@]}" \
  -d '{"provider": "slack_bot", "app_id": "A0APP", "team_id": "T0TEAM", "credential_ref": "slack/research-bot-token"}'

確認済みの Slack インストールがない場合は 409 SLACK_NOT_INSTALLED になる。インストールは運用者が上の登録 CLI で作成する。

Slack でアプリを削除すると連携は解除される。app_uninstalled イベント、または bot token を含む tokens_revoked イベントを受け取ると、インストールが revoked になり、そのインストール上の有効な連携も一緒に revoked になる(revoked_at が入る)。連携のチャンネル binding と認証情報の名前も解放される。コンソールの Slack タブは「接続済み」ではなく「再インストールが必要」を表示する。同じイベントが再度届いても、再インストールの後に遅れて届いても、状態は変わらない。再度インストールすると新しい有効な連携が作られ、以前の連携は解除済みの記録として残る。

イベントが届かない削除もある。bot token で Slack を呼ぶリクエスト(チャンネル一覧、名前の取得、チャンネル binding)が token_revoked または account_inactive で拒否された場合も、同じ解除を行う。応答は引き続き 409 SLACK_NOT_INSTALLED である。削除以外でも起こり得るエラー(invalid_auth、not_authed、token_expired)、429、5xx、通信失敗では状態を変えない。

チャンネル参加とデプロイの歓迎メッセージ

エージェントのチャンネル(PUT /v1/agents/{id}/slack-bindings)を保存すると、サーバーが bot をそのチャンネルに入れる。公開チャンネルには bot が自分で参加する。非公開チャンネルは Slack が bot の自己参加を許さないので、チャンネルで /invite @<bot> で招待する。招待されるとサーバーが検知して続きを進める。結果は binding の membership.state に出る。保存していないチャンネルに bot を招待した場合も、サーバーがそのチャンネルを使用中(on)で接続する。保存済みのチャンネルは設定を変えない(オフにしたチャンネルはオフのまま)。

エージェントがチャンネルに書いた返信は、そのチャンネルの全員が読む。担当チーム外の人も、ワークスペース設定でそのセッションをコンソールで見られない人も含まれる。コンソールのチャンネル欄にもそう書いてある。

membership.state意味
joinedbot がチャンネルにいる
awaiting_invite非公開チャンネルで、招待を待っている
config_errorSlack が token か scope を拒否した(error に Slack のエラーコード)。アプリを再インストールする
join_failedアーカイブ済みのチャンネルか、ワークスペースのポリシーが拒否した(error)

release が初めてデプロイされると、home チャンネルに안녕하세요, <エージェント名>입니다. 이 채널에 연결됐어요. 멘션하면 답할게요(既定は韓国語)が届く。この挨拶はエージェントごとに、チャンネルあたり一度だけ。新しい release のデプロイ、同じ定義の再提出、前の release へのロールバック、Slack 連携の切断と再接続のどれでも、挨拶済みのチャンネルには送らない。home をまだ挨拶していないチャンネルに移すと、そのチャンネルに一度届く。bot が home にまだいなければ、入るまで待つ。歓迎メッセージや参加が失敗してもデプロイは失敗しない。

次のステップ

このページ