# Open Claudia — Multi-Channel Plan (Telegram + Kazee Chat)

> This is a historical design plan. The target adapter architecture has since
> shipped; Section 1 records the current Open Claudia implementation, while the
> later rollout estimates and external-client notes are retained as design history.

## Goal
Make Open Claudia channel-agnostic, with Telegram as one adapter and **Kazee Chat** as a second equal-class adapter. Slash commands must work natively in both.

---

## 1. Current implementation

### open-claudia
- `bot.js` is the modular runtime bootstrap; Telegram, Kazee, and voice are channel adapters behind one normalized router and command registry.
- Async chat context carries the adapter, channel ID, and canonical user ID. Linked channels can resolve to one canonical identity and share provider/project state safely.
- All model-bearing paths call the provider-neutral runner. The provider registry owns Claude Code and OpenAI Codex capabilities, invocation arguments, event parsing, auth status, and native session semantics.
- Setup, web configuration, doctor, and status remain available with no provider installed. Model turns require one compatible authenticated provider.
- Provider-aware state, sessions, and scheduled jobs use the verified snapshot and rollback procedure in [PROVIDER_MIGRATION.md](./PROVIDER_MIGRATION.md).
- Shared operator stores such as the vault and soul remain intentionally global; conversations, settings, usage, and transcripts are canonical-user/project/provider scoped.

### Historical external-service baseline: chat-central (ActionHero + Socket.IO + Mongo + Redis)
- **Bot infrastructure already exists**:
  - `User.type: "Bot"` with `botToken` field (unique, indexed).
  - Auth middleware accepts `Authorization: Bearer bot_<token>` and bypasses Central JWT.
  - `BotRole` model with `webhookUrl` (for external webhook fan-out).
  - `createBot`, `listBots`, `createBotRole`, `listBotRoles` actions exist.
- Message model is rich: text/image/video/file/audio/poll/system, replyTo, mentions, reactions, edits, media[], read receipts.
- Real-time via Socket.IO V2 — events: `message:new`, `message:update`, `message:delete`, `typing`, `reaction:updated`, `pin:updated`, `chat:read`, etc.
- DMs: `isGroupChat: false`, 2 participants. Existing dedup query.
- File upload: `POST /chat/:chat_id/media` (MinIO-backed).
- **No native slash-command framework** — content is plain text. We need to build the slash menu UX in the clients.

### kazee-chat (web, Next.js + TipTap + Socket.IO V2)
- Rich text editor (TipTap) for input. Mentions autocomplete exists; **no slash menu yet**.
- Markdown rendering (react-markdown + rehype-highlight) — good for our outputs.
- File upload, voice recording, image rendering — all working.

### kazee-chat-mobile (React Native + Expo + Socket.IO V2)
- @-mention autocomplete in `ChatInputToolbar.tsx`; **no slash menu yet**.
- Voice recording via `expo-audio`; uploads via `mediaPipeline`.
- Markdown rendering via `react-native-markdown-display`.

---

## 2. Target architecture

### 2.1 ChannelAdapter interface (open-claudia)
A single interface every transport implements. The bot core becomes transport-agnostic and only talks to adapters.

```js
// channels/types.d.ts (conceptual)
class ChannelAdapter {
  readonly id;                              // "telegram" | "kazee"

  // Lifecycle
  start();                                  // connect/poll
  stop();

  // Inbound (emit to core)
  on("message",  fn(InboundMessage));
  on("command",  fn(InboundCommand));       // parsed slash command
  on("media",    fn(InboundMedia));         // voice/photo/document
  on("action",   fn(InboundAction));        // button click / callback

  // Outbound (called by core)
  send(channelId, text, opts);              // {parseMode, replyTo, buttons}
  edit(channelId, messageId, text, opts);
  delete(channelId, messageId);
  sendFile(channelId, path, caption);
  sendVoice(channelId, path);
  typing(channelId);

  // Identity
  canonicalUserId(rawId);                   // "telegram:123" / "kazee:abc"
  downloadMedia(mediaRef) -> localPath;
}
```

`InboundMessage` is a normalised envelope:
```ts
{ channelId, userId, canonicalUserId, text, replyToId, mediaRefs[], raw }
```

### 2.2 Bot core (transport-agnostic)
- `core/router.js` — receives normalised events, dispatches to command registry or chat handler.
- `core/commands.js` — explicit registry: `{name, description, args, handler}`. Both adapters consume the same registry; Telegram converts to `bot.onText` regex, Kazee exposes it via REST so clients can build the slash menu.
- `core/state.js` — already mostly transport-agnostic (sessions, vault, soul, crons). Stays.
- `core/identity.js` — `canonicalForChannel(transport, channelId)` already exists; generalise to `kazee:` prefix.

### 2.3 Refactor scope (bot.js → modules)
| New module | Lifted from bot.js (approx lines) | Notes |
|---|---|---|
| `channels/telegram/index.js` | client init 228-232, polling 238-250, all `bot.onText` 755-3091, media handlers 3259-3339, callback_query 3102-3255, `send/edit/delete/getFile` wrappers 986-1113 | Becomes a thin adapter |
| `channels/kazee/index.js` | NEW | Socket.IO V2 client + REST send + media download |
| `core/router.js` | NEW — dispatch logic from `bot.on("message")` 3343-3497 |
| `core/commands.js` | extract command handlers + descriptions | Each handler becomes `{name, desc, args, fn}` |
| `core/identity.js` | 376-392, 487-491, 561-589 | Generalised |
| `core/transcripts.js` | already split (project-transcripts.js) | No change |
| `core/state.js` | 501-503, 597-649 | No change |

bot.js shrinks to ~200 lines of bootstrap.

---

## 3. Kazee Chat adapter — concrete plan

### 3.1 Bot account setup (one-time, ops)
On chat-central:
1. `POST /botRole` → `{botName: "open-claudia", webhookUrl: null}` (we won't use webhook; we use Socket.IO).
2. `POST /bot` → creates `User{type:"Bot", botToken, botRole, name:"Open Claudia"}`.
3. Store `botToken` in open-claudia env (`KAZEE_BOT_TOKEN`) — never in code.

### 3.2 Connect (open-claudia → chat-central)
- Open Socket.IO V2 connection: `wss://<chat-central>/socket.io?version=v2` with `Authorization: Bearer bot_<token>`.
- On connect: `GET /chats?userId=<botUserId>` → list of chats the bot is in.
- Batch-join those chat rooms (same pattern web/mobile use).
- Listen to `message:new`. Filter: ignore own messages, process DMs + group messages mentioning bot.

### 3.3 Identity
- Canonical user ID: `kazee:<userId>` (chat-central User._id).
- Per-user transcripts: same isolation logic as Telegram — `projectHash` already userId-scoped.

### 3.4 Send / edit / delete
- Send: `POST /chat/:chatId/message` with `{content, type:"text"}` or `{type:"image", mediaIds}`.
- Edit: there's no documented edit endpoint in the explore report; we either (a) add `PATCH /message/:id` to chat-central, or (b) skip in-place edits and just send a follow-up. **Recommend (a)** — chat already supports `edited` + `editHistory` in the model.
- Delete: similar — likely needs a `DELETE /message/:id` action if not present. **Confirm in chat-central** before assuming.

### 3.5 Voice / files
- Receive: when `message:new` arrives with `type:"audio"`/`"voice"`, fetch media URL, download via REST, run Whisper (same as today's `transcribeAudio`).
- Send file: `POST /chat/:chatId/media` (returns mediaId) → `POST /chat/:chatId/message` with `mediaIds:[id]`.

### 3.6 Buttons / callback queries (the awkward gap)
Telegram inline keyboards have no Kazee equivalent today. Used in open-claudia for: auth approval, project picker, model/backend/effort selection, session switcher, onboarding style, cron presets.

Three options, ranked:
1. **Recommended — add a `type:"interactive"` message kind to chat-central** with a `buttons:[{label, callbackId}]` field, plus a `message:action` socket event when a user clicks. Render natively in web + mobile. Reuses existing message infra. Highest UX quality.
2. **Text-fallback** — bot sends `Reply with 1/2/3` style menus, parses replies. No frontend work, ugly UX, fragile when humans free-type.
3. **Web-only mini-form** — bot embeds a special markdown block (`:::buttons[...]:::`), web/mobile parse and render. Lighter than (1), heavier than (2). Drifts from chat-central message model.

Decision needed before implementation — flagging this as the biggest design choice.

### 3.7 Markdown
- Standard Markdown — react-markdown / react-native-markdown-display already render `**bold**`, `_italic_`, `` `code` ``, fenced code, lists. Bot's existing Telegram-Markdown output is mostly compatible; minor: Telegram uses `*bold*` (single asterisk). The adapter normalises asterisks on the way out.

---

## 4. Slash commands — UX across clients

### 4.1 Server side (chat-central, new)
- Add `commands: [{name, description, args:[{name, required}]}]` to `User` (for Bot type) or to `BotRole`. Prefer `BotRole.commands`.
- New action: `GET /bot/:userId/commands` → returns the list.
- Bot registers/updates its command list on startup via `PUT /botRole/:id/commands`.
- Estimated effort: 1 model field, 2 actions, ~1 day in chat-central.

### 4.2 Web (kazee-chat) — slash menu in `ChatInput.tsx`
- TipTap extension or input-level handler that:
  - Detects `/` as first character of an empty message.
  - Calls `GET /bot/:botUserId/commands` if the other party is a Bot user.
  - Shows a floating dropdown (same component family as mentions menu).
  - On selection: inserts the slash command name, leaves cursor for args.
- Caches command list per bot for the session.
- Submit sends `content: "/cmd arg1 arg2"` as normal text — bot parses on its side.
- Estimated effort: ~2 days.

### 4.3 Mobile (kazee-chat-mobile) — slash menu in `ChatInputToolbar.tsx`
- Same logic, RN implementation. Reuse the mentions autocomplete pattern that already exists.
- Floating list above keyboard.
- Estimated effort: ~2 days.

### 4.4 Bot side (open-claudia)
- Parse `/cmd args` from inbound text. `core/commands.js` registry already handles this — no Telegram dependency.
- Same handlers work on both adapters.

---

## 5. Phased rollout

**Phase 0 — prep (no behaviour change)**
- Refactor `bot.js` to extract `core/commands.js`, `core/router.js`, `channels/telegram/`. All existing functionality goes through the new abstraction. Ship as v1.21.0. Verify Telegram still works.

**Phase 1 — chat-central additions**
- Add `BotRole.commands` field + `GET /bot/:id/commands`.
- Add `PATCH /message/:id` (edit) + `DELETE /message/:id` (delete) actions, if not present.
- Decision on interactive buttons (§3.6). If option 1 (new message type), spec + implement.

**Phase 2 — kazee adapter**
- Build `channels/kazee/index.js` in open-claudia.
- Provision bot account in chat-central (dev env first).
- Connect, listen, send text. Parity with Telegram for plain text + file + voice.
- Ship as v1.22.0 behind `KAZEE_ENABLED=true` env flag.

**Phase 3 — slash menu UX**
- Web: slash menu in `ChatInput.tsx`.
- Mobile: slash menu in `ChatInputToolbar.tsx`.
- Bot registers its full command list (currently 35+).

**Phase 4 — interactive buttons (if option 1 chosen)**
- chat-central: `type:"interactive"` message + `message:action` event.
- Web + mobile: render buttons in `MessageBubble`, emit action on tap.
- Bot: emit interactive menus instead of text for auth/project/model selection.

**Phase 5 — production**
- Flip `KAZEE_ENABLED` on in prod.
- Document onboarding: how a Kazee user starts a chat with Open Claudia and authenticates.

---

## 6. Onboarding flow (Kazee)

1. User opens Kazee, searches for "Open Claudia" in users.
2. Creates DM (existing flow: `createChat` with bot as participant).
3. Bot receives `message:new` for the first message → triggers `/start`-equivalent: replies with auth instructions.
4. Auth: same model as Telegram today (vault-stored credentials, OAuth flow). No changes — `canonicalUserId = kazee:<userId>` ensures isolation.
5. Subsequent messages flow normally.

---

## 7. Risks & open questions

| # | Risk | Mitigation |
|---|---|---|
| 1 | Edit/delete endpoints may not exist in chat-central today | Verify before Phase 1; add if missing |
| 2 | Telegram inline keyboards have no equivalent — biggest design call | Decide §3.6 option before Phase 4 |
| 3 | Markdown dialect drift (Telegram `*bold*` vs standard `**bold**`) | Normalise in `channels/telegram/format.js` and `channels/kazee/format.js` |
| 4 | Long-message chunking (4KB Telegram limit) doesn't apply to Kazee | Move chunking into Telegram adapter only |
| 5 | Voice transcription path coupled to TEMP_DIR + ffmpeg + whisper | Already path-based; just changes how media is fetched (REST vs Telegram API) |
| 6 | Bot in group chats: noise if it processes every message | Only respond when @mentioned or in DM (chat-central `mentions[]` already supports this) |
| 7 | Onboarding identity binding — how do we know which Kazee user is which workspace operator? | `/auth` flow stays the same; first message after `/auth` binds canonical user to bot state |

---

## 8. Work breakdown (rough)

| Area | Effort | Owner |
|---|---|---|
| Phase 0 — bot.js refactor | 3-4 days | open-claudia |
| Phase 1 — chat-central additions (commands list + edit/delete) | 2 days | chat-central |
| Phase 2 — Kazee adapter (open-claudia) | 3 days | open-claudia |
| Phase 3 — slash menu (web) | 2 days | kazee-chat |
| Phase 3 — slash menu (mobile) | 2 days | kazee-chat-mobile |
| Phase 4 — interactive buttons (optional but recommended) | 4 days across 3 repos | shared |
| **Total core (Phases 0–3)** | **~12 days** | |
| **With Phase 4** | **~16 days** | |

---

## 9. Decisions to lock before starting

1. **Interactive buttons** — option 1, 2, or 3 from §3.6? Affects all three frontends and chat-central scope.
2. **Bot scope in group chats** — DMs only initially, or mention-driven in groups too?
3. **Command registration** — does bot register commands on every startup, or one-shot via ops script?
4. **Edit/delete endpoints in chat-central** — confirm they exist; if not, add to Phase 1.
5. **`KAZEE_ENABLED` flag** — env-flag rollout vs straight cutover.
