Skip to content

Slack アプリ (招待ワークフロー + 会話アシスタント)

apps/slack (package @tied-workspace/slack、Worker 名 tied-workspace-slack) の仕様書。

Slack の Events API / スラッシュコマンド / interactivity をすべて HTTP で受ける単一の公開 Cloudflare Worker。Cloudflare Access の外に置き、真正性は Slack 署名検証で担保する (suteki-bot / noisy-crow-events と同型)。1 つの Worker が次の 2 機能を担う。

  1. 招待ワークフロー — √N ルール + ソシオクラシーの同意原則によるメンバー招待 (スラッシュコマンド + モーダル + 告知 + cron 解決)。
  2. 会話アシスタント@メンション と DM に Cloudflare Workers AI で日本語応答する (tied-inc/tied-bot のコア会話を再現)。

tied-ai-bot 移行計画との関係

tied-bot 統合計画 (tied-ai-bot) は、tied-bot の全機能 (専門エージェント網・メモリ・リアクション分析・週次レポート等) を Cloudflare Agents SDK + AI Gateway + D1/R2/Vectorize で再構成する将来計画です。本アプリはそれより意図的に軽量な範囲apps/slack 内で実装します。

  • スコープ: コア会話のみ (専門エージェント・メモリ・分析・ファイル解析は持たない)。
  • 推論: Cloudflare Workers AI (外部 API キー不要)。AI Gateway 経由 gpt-5-mini ではない。
  • 処理トポロジー: Agents SDK (Durable Object) ではなく ctx.waitUntil。単発の Workers AI 推論は 30 秒以内に収まるため。マルチステップのツール実行やメモリを伴う完全再現が必要になった段階で、tied-ai-bot 計画 (DO + AI Gateway) へ発展させる。

設計の不変条件

  1. クリーンアーキテクチャ: domainapplicationinfrastructure / interfaces の依存方向を守る。domain / application は Slack / Cloudflare の SDK をインポートしない。
  2. Slack への ack は 3 秒以内。署名検証 → 即時応答し、重い処理 (AI 応答・告知投稿・welcome・取り消し) は ctx.waitUntil に逃がす。
  3. 公開エンドポイントは署名検証必須 (SLACK_SIGNING_SECRET、HMAC-SHA256、5 分リプレイ窓)。Socket Mode は使わない。
  4. 認証ロジックをアプリに書かない。真正性は Slack 署名のみで担保する (この Worker は Cloudflare Access の外)。
  5. 相対 import に拡張子を付けない (moduleResolution: "bundler")。

ルーティング

メソッド / パス役割
POST /slack/eventsEvents API (app_mention, message.im, member_joined_channel)。url_verification チャレンジに対応
POST /slack/commandsスラッシュコマンド (/invite-proposal, /invitation-config)
POST /slack/interactionsモーダル送信 (view_submission) / ボタン (block_actions)
GET /health死活監視
cron 0 * * * *scheduled ハンドラが待機期間を過ぎた招待提案を解決する

すべての POST はまず src/verify-signature.ts で署名検証する。検証失敗は 401。

レイヤー構成

src/
├── domain/
│   ├── invitation/                 # Proposal / √N ルール / 設定 (純粋ロジック)
│   └── assistant/message.ts        # ChatMessage 型 / URL 抽出 / メンション除去
├── application/
│   ├── invitation/                 # propose / submit-alert / cancel / resolve-due / configure
│   ├── welcome/run-welcome-flow.ts # times チャンネル作成 + 案内投稿
│   └── assistant/
│       ├── ports.ts                # ChatModel / UrlFetcher / AssistantConversation
│       ├── reply.ts                # 会話応答ユースケース
│       └── system-prompt.ts        # 日本語システムプロンプト
├── infrastructure/
│   ├── kv/                         # BindingKvStore + 各リポジトリ
│   ├── slack/                      # WebClient ベースの announcer / dm / member / conversation + Block Kit
│   ├── ai/workers-ai-chat-model.ts # Workers AI (env.AI) 実装
│   └── http/url-fetcher.ts         # URL 本文取得 (HTML タグ除去)
├── interfaces/slack/
│   ├── commands.ts                 # スラッシュコマンド振り分け
│   ├── interactions.ts             # interactivity 振り分け
│   ├── events.ts                   # Events API 振り分け
│   └── views/                      # Block Kit モーダル定義
├── env.ts                          # Env (bindings) + dep ファクトリ
├── index.ts                        # Worker fetch ルーティング + scheduled
└── verify-signature.ts             # Slack 署名検証

@slack/web-apiWebClientnodejs_compat フラグで Workers 上で動作する (noisy-crow-events と同じ前提)。

会話アシスタント (会話のコア)

  1. app_mention または DM (message.im) を受信 (bot 自身・subtype 付き・bot 投稿は無視)。
  2. スレッド内なら履歴を取得して文脈に使う (conversations.replies、bot の発言は assistant role)。
  3. 「考え中…」プレースホルダを投稿する (chat.postMessage)。
  4. 本文中の URL を取得し、本文テキストを文脈に追加する (最大 3 件)。
  5. Workers AI (env.AI.run) で日本語応答を生成する (既定モデル @cf/meta/llama-3.3-70b-instruct-fp8-fastWORKERS_AI_MODEL で上書き可)。
  6. プレースホルダを最終応答へ更新する (chat.update)。生成失敗時はエラー文言にフォールバック。

招待ワークフロー

apps/slack の元機能。Socket Mode から HTTP へ移行しただけで挙動は不変。

  • /invite-proposal (権限: authorizedProposers) → 提案モーダルを開く (views.open)。
  • モーダル送信 (view_submission) → √N ルールを検証。不足ならエラーで再入力、合格なら ctx.waitUntil で告知投稿 + 提案者 DM。
  • 告知の「アラートを申告する」ボタン → アラートモーダル → 1 件でもアラートが入れば管理者へ DM。
  • 管理者の「告知を取り消す」ボタン → cancelProposalresponse_url で元メッセージ更新。
  • /invitation-config (権限: admin) → 告知チャンネル / 待機日数 / 管理者 / 提案権限を KV で管理。
  • cron が待機期間経過かつ無アラートの提案を approved に遷移させ、告知チャンネルと提案者へ通知。

詳細な招待ポリシーは メンバーシップ を参照。

バインディング / シークレット / 設定

種別名前用途
KVPROPOSALS_KV招待提案 / 設定 (invitation:*) + イベント重複抑止 (event:<id>、TTL 1h)
Workers AIAI会話アシスタントの推論
secretSLACK_BOT_TOKENchat.postMessage / chat.update / views.open / conversations.*
secretSLACK_SIGNING_SECRET全リクエストの署名検証
varWORKERS_AI_MODEL推論モデルの上書き (任意)
varWELCOME_*welcome フローのチャンネル / usergroup / URL (未設定なら welcome は起動しない)

運用パラメータ (告知チャンネル / 待機日数 / 管理者 / 提案権限) は KV (invitation:settings) に置き、/invitation-config から変更する。ワークスペース owner は KV が空でも常に admin として扱う (lockout 防止)。

ローカル開発 / デプロイ

bash
bun run --filter @tied-workspace/slack dev        # wrangler dev
bun run --filter @tied-workspace/slack test       # bun test (coverage gate)
bun run --filter @tied-workspace/slack typecheck   # tsc --noEmit
bun run --filter @tied-workspace/slack deploy      # wrangler deploy

デプロイ後、Slack App 側で 3 つの Request URL をこの Worker に向け、Events を購読する。

Event Subscriptions Request URL       → https://<worker>/slack/events
Slash Commands Request URL            → https://<worker>/slack/commands
Interactivity & Shortcuts Request URL → https://<worker>/slack/interactions
購読イベント: app_mention, message.im, member_joined_channel

# Secret 設定
bunx wrangler@^4.34.0 secret put SLACK_BOT_TOKEN      -c apps/slack/wrangler.toml
bunx wrangler@^4.34.0 secret put SLACK_SIGNING_SECRET -c apps/slack/wrangler.toml

# Slack 側初期設定
/invitation-config channel #all-general
/invitation-config proposer add @yamamoto