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 機能を担う。
- 招待ワークフロー — √N ルール + ソシオクラシーの同意原則によるメンバー招待 (スラッシュコマンド + モーダル + 告知 + cron 解決)。
- 会話アシスタント —
@メンションと 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) へ発展させる。
設計の不変条件
- クリーンアーキテクチャ:
domain→application→infrastructure/interfacesの依存方向を守る。domain/applicationは Slack / Cloudflare の SDK をインポートしない。 - Slack への ack は 3 秒以内。署名検証 → 即時応答し、重い処理 (AI 応答・告知投稿・welcome・取り消し) は
ctx.waitUntilに逃がす。 - 公開エンドポイントは署名検証必須 (
SLACK_SIGNING_SECRET、HMAC-SHA256、5 分リプレイ窓)。Socket Mode は使わない。 - 認証ロジックをアプリに書かない。真正性は Slack 署名のみで担保する (この Worker は Cloudflare Access の外)。
- 相対 import に拡張子を付けない (
moduleResolution: "bundler")。
ルーティング
| メソッド / パス | 役割 |
|---|---|
POST /slack/events | Events 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-api の WebClient は nodejs_compat フラグで Workers 上で動作する (noisy-crow-events と同じ前提)。
会話アシスタント (会話のコア)
app_mentionまたは DM (message.im) を受信 (bot 自身・subtype付き・bot 投稿は無視)。- スレッド内なら履歴を取得して文脈に使う (
conversations.replies、bot の発言はassistantrole)。 - 「考え中…」プレースホルダを投稿する (
chat.postMessage)。 - 本文中の URL を取得し、本文テキストを文脈に追加する (最大 3 件)。
- Workers AI (
env.AI.run) で日本語応答を生成する (既定モデル@cf/meta/llama-3.3-70b-instruct-fp8-fast、WORKERS_AI_MODELで上書き可)。 - プレースホルダを最終応答へ更新する (
chat.update)。生成失敗時はエラー文言にフォールバック。
招待ワークフロー
apps/slack の元機能。Socket Mode から HTTP へ移行しただけで挙動は不変。
/invite-proposal(権限:authorizedProposers) → 提案モーダルを開く (views.open)。- モーダル送信 (
view_submission) → √N ルールを検証。不足ならエラーで再入力、合格ならctx.waitUntilで告知投稿 + 提案者 DM。 - 告知の「アラートを申告する」ボタン → アラートモーダル → 1 件でもアラートが入れば管理者へ DM。
- 管理者の「告知を取り消す」ボタン →
cancelProposal→response_urlで元メッセージ更新。 /invitation-config(権限: admin) → 告知チャンネル / 待機日数 / 管理者 / 提案権限を KV で管理。- cron が待機期間経過かつ無アラートの提案を
approvedに遷移させ、告知チャンネルと提案者へ通知。
詳細な招待ポリシーは メンバーシップ を参照。
バインディング / シークレット / 設定
| 種別 | 名前 | 用途 |
|---|---|---|
| KV | PROPOSALS_KV | 招待提案 / 設定 (invitation:*) + イベント重複抑止 (event:<id>、TTL 1h) |
| Workers AI | AI | 会話アシスタントの推論 |
| secret | SLACK_BOT_TOKEN | chat.postMessage / chat.update / views.open / conversations.* 等 |
| secret | SLACK_SIGNING_SECRET | 全リクエストの署名検証 |
| var | WORKERS_AI_MODEL | 推論モデルの上書き (任意) |
| var | WELCOME_* | welcome フローのチャンネル / usergroup / URL (未設定なら welcome は起動しない) |
運用パラメータ (告知チャンネル / 待機日数 / 管理者 / 提案権限) は KV (invitation:settings) に置き、/invitation-config から変更する。ワークスペース owner は KV が空でも常に admin として扱う (lockout 防止)。
ローカル開発 / デプロイ
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