mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-22 21:52:28 +00:00
apps: support bot relocate
This commit is contained in:
1 parent
2909024078
commit
519268deac
16 files changed
+222
-1491
No files matched your search
File diff suppressed because it is too large.
Load diff
File renamed without changes.
@@ -0,0 +1,143 @@
|
||||
# SimpleX Chat — Context for AI Assistant
|
||||
|
||||
## What is SimpleX Chat?
|
||||
|
||||
SimpleX Chat is a private and secure messaging platform. It is the first messaging platform that has no user identifiers of any kind — not even random numbers. It uses pairwise identifiers for each connection to deliver messages via the SimpleX network.
|
||||
|
||||
### Core Privacy Guarantees
|
||||
|
||||
- **No user identifiers**: No phone numbers, usernames, or account IDs. Users connect via one-time invitation links or QR codes.
|
||||
- **End-to-end encryption**: All messages use double ratchet protocol with post-quantum key exchange (ML-KEM). Even if encryption keys are compromised in the future, past messages remain secure.
|
||||
- **No metadata access**: Relay servers cannot correlate senders and receivers — each conversation uses separate unidirectional messaging queues with different addresses on each side.
|
||||
- **Decentralized**: No central server. Messages are relayed through SMP (SimpleX Messaging Protocol) servers. Users can choose or self-host their own servers.
|
||||
- **Open source**: All client and server code is available on GitHub under AGPL-3.0 license. The protocol design is published and peer-reviewed.
|
||||
- **No global identity**: There is no way to discover users on the platform — you can only connect to someone if they share a link or QR code with you.
|
||||
|
||||
## Available Platforms
|
||||
|
||||
- **Mobile**: iOS (App Store), Android (Google Play, F-Droid, APK)
|
||||
- **Desktop**: macOS, Windows, Linux (AppImage, deb, Flatpak)
|
||||
- All platforms support the same features and can be used simultaneously with linked devices
|
||||
|
||||
## Key Features
|
||||
|
||||
### Messaging
|
||||
- Text messages with markdown formatting
|
||||
- Voice messages
|
||||
- Images and videos
|
||||
- File sharing (any file type, up to 1GB via XFTP)
|
||||
- Message reactions and replies
|
||||
- Message editing and deletion
|
||||
- Disappearing messages (configurable per contact/group)
|
||||
- Live messages (recipient sees you typing in real-time)
|
||||
- Message delivery receipts
|
||||
|
||||
### Calls
|
||||
- End-to-end encrypted audio and video calls
|
||||
- Calls work peer-to-peer when possible, relayed through TURN servers otherwise
|
||||
- WebRTC-based
|
||||
|
||||
### Groups
|
||||
- Group chats with roles: owner, admin, moderator, member, observer
|
||||
- Groups can have hundreds of members
|
||||
- Group links for easy joining
|
||||
- Group moderation tools
|
||||
- Business chat groups for customer support
|
||||
|
||||
### Privacy Features
|
||||
- **Incognito mode**: Use a random profile name per contact — your real profile is never shared
|
||||
- **Multiple chat profiles**: Maintain separate identities
|
||||
- **Hidden profiles**: Protect profiles with a password
|
||||
- **Contact verification**: Verify contacts via security code comparison
|
||||
- **SimpleX Lock**: App lock with passcode or biometric
|
||||
- **Private routing**: Route messages through multiple servers to hide your IP from destination servers
|
||||
- **No tracking or analytics**: The app does not collect or send any telemetry
|
||||
|
||||
### Device & Data Management
|
||||
- **Database export/import**: Migrate to a new device by exporting the database (encrypted or unencrypted)
|
||||
- **Database passphrase**: Encrypt the local database with a passphrase
|
||||
- **Linked devices**: Use SimpleX on multiple devices simultaneously (mobile + desktop)
|
||||
- **Chat archive**: Export and import full chat history
|
||||
|
||||
## SimpleX Network Architecture
|
||||
|
||||
### SMP (SimpleX Messaging Protocol)
|
||||
- Asynchronous message delivery via relay servers
|
||||
- Each conversation uses **separate unidirectional messaging queues**
|
||||
- Queues have different addresses on sender and receiver sides — servers cannot correlate them
|
||||
- Messages are end-to-end encrypted; servers only see encrypted blobs
|
||||
- Servers do not store any user profiles or contact lists
|
||||
- Messages are deleted from servers once delivered
|
||||
|
||||
### XFTP (SimpleX File Transfer Protocol)
|
||||
- Used for large files (images, videos, documents)
|
||||
- Files are encrypted, split into chunks, and sent through multiple relay servers
|
||||
- Temporary file storage — files are deleted after download or expiry
|
||||
|
||||
### Server Architecture
|
||||
- **Preset servers**: SimpleX Chat Inc. operates preset relay servers, but they can be changed
|
||||
- **Self-hosting**: Users can run their own SMP and XFTP servers
|
||||
- **No federation**: Servers don't communicate with each other. Each message queue is independent
|
||||
- **Tor support**: SimpleX supports connecting through Tor for additional IP privacy
|
||||
|
||||
## Comparison with Other Messengers
|
||||
|
||||
### vs Signal
|
||||
- SimpleX requires no phone number or any identifier to register
|
||||
- SimpleX is decentralized — Signal has a central server
|
||||
- SimpleX relay servers cannot access metadata (who talks to whom) — Signal's server knows your contacts
|
||||
- Both use strong end-to-end encryption
|
||||
|
||||
### vs Telegram
|
||||
- SimpleX is fully end-to-end encrypted for all chats — Telegram only encrypts "secret chats"
|
||||
- SimpleX has no phone number requirement
|
||||
- SimpleX is fully open source (clients and servers) — Telegram server is closed source
|
||||
- SimpleX collects no metadata
|
||||
|
||||
### vs Matrix/Element
|
||||
- SimpleX has better metadata privacy — Matrix servers see who is in which room
|
||||
- SimpleX is simpler to use — no server selection or account creation
|
||||
- SimpleX does not use federated identity
|
||||
|
||||
### vs Session
|
||||
- SimpleX doesn't use a blockchain or cryptocurrency
|
||||
- SimpleX has better group support and more features
|
||||
- Both have no phone number requirement
|
||||
|
||||
## Common User Questions & Troubleshooting
|
||||
|
||||
### Getting Started
|
||||
- **How do I add contacts?** Create a one-time invitation link (or QR code) and share it with your contact. They open it in their SimpleX app to connect. Links are single-use by default for maximum privacy, but you can create reusable address links.
|
||||
- **Can I use SimpleX without a phone number?** Yes, SimpleX requires no phone number, email, or any identifier. Just install the app and start chatting.
|
||||
- **How do I join a group?** Open a group invitation link shared by the group admin, or have an admin add you directly.
|
||||
|
||||
### Device Migration
|
||||
- **How do I move to a new phone?** Go to Settings > Database > Export database. Transfer the file to your new device, install SimpleX, and import the database. Note: you should stop using the old device after export to avoid message duplication.
|
||||
- **Can I use SimpleX on multiple devices?** Yes, link a desktop app to your mobile app. Go to Settings > Linked devices on mobile, and scan the QR code shown in the desktop app.
|
||||
|
||||
### Privacy & Security
|
||||
- **Can SimpleX servers read my messages?** No. All messages are end-to-end encrypted. Servers only relay encrypted data and cannot decrypt it.
|
||||
- **Can SimpleX see who I'm talking to?** No. Each conversation uses separate queues with different addresses. Servers cannot correlate senders and receivers.
|
||||
- **How do I verify my contact?** Open the contact's profile, tap "Verify security code", and compare the code with your contact (in person or via another channel).
|
||||
- **What is incognito mode?** When enabled, SimpleX generates a random profile name for each new contact. Your real profile name is never shared. Enable it in Settings > Incognito.
|
||||
|
||||
### Servers
|
||||
- **How do I self-host a server?** Follow the guide at https://simplex.chat/docs/server.html. You need a Linux server with a public IP. Install the SMP server package and configure it.
|
||||
- **How do I change relay servers?** Go to Settings > Network & servers. You can add your own server addresses and disable preset servers.
|
||||
- **Do I need to use SimpleX's servers?** No. You can use any SMP/XFTP servers, including your own. However, you and your contacts need to be able to reach each other's servers.
|
||||
|
||||
### Troubleshooting
|
||||
- **Messages not delivering?** Check your internet connection. Try switching between WiFi and mobile data. Go to Settings > Network & servers and check server status. You can also try restarting the app.
|
||||
- **Cannot connect to a contact?** The invitation link may have expired or already been used. Create a new invitation link and share it again.
|
||||
- **App is slow?** Large databases can slow down the app. Consider archiving old chats or deleting unused contacts/groups.
|
||||
- **Notifications not working (Android)?** SimpleX needs to run a background service for notifications. Go to Settings > Notifications and enable background service. You may need to disable battery optimization for the app.
|
||||
- **Notifications not working (iOS)?** Ensure notifications are enabled in iOS Settings > SimpleX Chat. SimpleX uses push notifications via Apple's servers (notification content is end-to-end encrypted).
|
||||
|
||||
## Links
|
||||
|
||||
- Website: https://simplex.chat
|
||||
- GitHub: https://github.com/simplex-chat
|
||||
- Documentation: https://simplex.chat/docs
|
||||
- Server setup: https://simplex.chat/docs/server.html
|
||||
- Protocol whitepaper: https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md
|
||||
- Security audit: https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html
|
||||
Generated
File renamed without changes.
File renamed without changes.
@@ -112,7 +112,7 @@ type ConversationState =
|
||||
| {type: "welcome"}
|
||||
| {type: "teamQueue"; userMessages: string[]}
|
||||
| {type: "grokMode"; grokMemberGId: number; history: GrokMessage[]}
|
||||
| {type: "teamPending"; teamMemberGId: number; grokMemberGId?: number; history?: GrokMessage[]}
|
||||
| {type: "teamPending"; teamMemberGId: number}
|
||||
| {type: "teamLocked"; teamMemberGId: number}
|
||||
```
|
||||
|
||||
@@ -123,12 +123,11 @@ type ConversationState =
|
||||
welcome ──(1st user msg)──> teamQueue
|
||||
teamQueue ──(user msg)──> teamQueue (append to userMessages)
|
||||
teamQueue ──(/grok)──> grokMode (userMessages → initial Grok history)
|
||||
teamQueue ──(/team)──> teamPending
|
||||
teamQueue ──(/team)──> teamPending (add team member)
|
||||
grokMode ──(user msg)──> grokMode (forward to Grok API, append to history)
|
||||
grokMode ──(/team)──> teamPending (carry grokMemberGId + history)
|
||||
teamPending ──(team member msg)──> teamLocked (remove Grok if present)
|
||||
teamPending ──(/grok, grok present)──> teamPending (forward to Grok, still usable)
|
||||
teamPending ──(/grok, no grok)──> reply "team mode"
|
||||
grokMode ──(/team)──> teamPending (remove Grok immediately, add team member)
|
||||
teamPending ──(team member msg)──> teamLocked
|
||||
teamPending ──(/grok)──> reply "team mode"
|
||||
teamLocked ──(/grok)──> reply "team mode", stay locked
|
||||
teamLocked ──(any)──> no action (team sees directly)
|
||||
```
|
||||
@@ -227,11 +226,13 @@ await grokChat.startChat()
|
||||
|-------|---------|--------|
|
||||
| `acceptingBusinessRequest` | `onBusinessRequest` | `conversations.set(groupInfo.groupId, {type: "welcome"})` |
|
||||
| `newChatItems` | `onNewChatItems` | For each chatItem: identify sender, extract text, dispatch to routing |
|
||||
| `leftMember` | `onLeftMember` | If customer left → delete state. If team member left → revert to teamQueue. If Grok left → clear grokMemberGId. |
|
||||
| `leftMember` | `onLeftMember` | If customer left → delete state. If team member left → revert to teamQueue. If Grok left during grokMode → revert to teamQueue. |
|
||||
| `deletedMemberUser` | `onDeletedMemberUser` | Bot removed from group → delete state |
|
||||
| `groupDeleted` | `onGroupDeleted` | Delete state, delete grokGroupMap entry |
|
||||
| `connectedToGroupMember` | `onMemberConnected` | Log for debugging |
|
||||
|
||||
We do NOT use `onMessage`/`onCommands` from `bot.run()` — all routing is done in the `newChatItems` event handler for full control over state-dependent command handling.
|
||||
|
||||
**Sender identification in `newChatItems`:**
|
||||
```typescript
|
||||
for (const ci of evt.chatItems) {
|
||||
@@ -248,10 +249,9 @@ for (const ci of evt.chatItems) {
|
||||
const sender = chatItem.chatDir.groupMember
|
||||
|
||||
const isCustomer = sender.memberId === groupInfo.businessChat.customerId
|
||||
const isTeamMember = state.type === "teamPending" || state.type === "teamLocked"
|
||||
? sender.groupMemberId === state.teamMemberGId
|
||||
: false
|
||||
const isGrok = (state.type === "grokMode" || state.type === "teamPending")
|
||||
const isTeamMember = (state.type === "teamPending" || state.type === "teamLocked")
|
||||
&& sender.groupMemberId === state.teamMemberGId
|
||||
const isGrok = state.type === "grokMode"
|
||||
&& state.grokMemberGId === sender.groupMemberId
|
||||
|
||||
if (isGrok) continue // skip Grok messages (we sent them via grokChat)
|
||||
@@ -260,12 +260,18 @@ for (const ci of evt.chatItems) {
|
||||
}
|
||||
```
|
||||
|
||||
**Text extraction:**
|
||||
**Command detection** — use `util.ciBotCommand()` for `/grok` and `/team`; all other text (including unrecognized `/commands`) is routed as "other text" per spec ("Unrecognized commands: treated as normal messages in the current mode"):
|
||||
```typescript
|
||||
function extractText(chatItem: T.ChatItem): string | null {
|
||||
const text = util.ciContentText(chatItem)
|
||||
return text?.trim() || null
|
||||
}
|
||||
|
||||
// In onCustomerMessage:
|
||||
const cmd = util.ciBotCommand(chatItem)
|
||||
if (cmd?.keyword === "grok") { /* handle /grok */ }
|
||||
else if (cmd?.keyword === "team") { /* handle /team */ }
|
||||
else { /* handle as normal text message, including unrecognized /commands */ }
|
||||
```
|
||||
|
||||
## 9. Message Routing Table
|
||||
@@ -279,10 +285,9 @@ function extractText(chatItem: T.ChatItem): string | null {
|
||||
| `teamQueue` | `/team` | Add team member | `mainChat.apiAddMember(groupId, teamContactId, "member")` + `mainChat.apiSendTextMessage([Group, groupId], teamAddedMsg)` | `teamPending` |
|
||||
| `teamQueue` | other text | Forward to team, append to userMessages | `mainChat.apiSendTextMessage([Group, teamGroupId], fwd)` | `teamQueue` |
|
||||
| `grokMode` | `/grok` | Ignore (already in grok mode) | — | `grokMode` |
|
||||
| `grokMode` | `/team` | Add team member (keep Grok for now) | `mainChat.apiAddMember(groupId, teamContactId, "member")` + `mainChat.apiSendTextMessage([Group, groupId], teamAddedMsg)` | `teamPending` (carry grokMemberGId + history) |
|
||||
| `grokMode` | other text | Forward to Grok API + team | Grok API call + `grokChat.apiSendTextMessage([Group, grokLocalGId], response)` + `mainChat.apiSendTextMessage([Group, teamGroupId], fwd)` | `grokMode` (append history) |
|
||||
| `teamPending` (grok present) | `/grok` | Forward to Grok (still usable) | Grok API call + `grokChat.apiSendTextMessage(...)` | `teamPending` |
|
||||
| `teamPending` (no grok) | `/grok` | Reply "team mode" | `mainChat.apiSendTextMessage([Group, groupId], teamLockedMsg)` | `teamPending` |
|
||||
| `grokMode` | `/team` | Remove Grok, add team member | `mainChat.apiRemoveMembers(groupId, [grokMemberGId])` + `mainChat.apiAddMember(groupId, teamContactId, "member")` + `mainChat.apiSendTextMessage([Group, groupId], teamAddedMsg)` | `teamPending` |
|
||||
| `grokMode` | other text | Forward to Grok API + forward to team | Grok API call + `grokChat.apiSendTextMessage([Group, grokLocalGId], response)` + `mainChat.apiSendTextMessage([Group, teamGroupId], fwd)` | `grokMode` (append history) |
|
||||
| `teamPending` | `/grok` | Reply "team mode" | `mainChat.apiSendTextMessage([Group, groupId], teamLockedMsg)` | `teamPending` |
|
||||
| `teamPending` | `/team` | Ignore (already team) | — | `teamPending` |
|
||||
| `teamPending` | other text | No forwarding (team sees directly in group) | — | `teamPending` |
|
||||
| `teamLocked` | `/grok` | Reply "team mode" | `mainChat.apiSendTextMessage([Group, groupId], teamLockedMsg)` | `teamLocked` |
|
||||
@@ -302,13 +307,18 @@ async forwardToTeam(groupId: number, groupInfo: T.GroupInfo, text: string): Prom
|
||||
}
|
||||
|
||||
async activateTeam(groupId: number, state: ConversationState): Promise<void> {
|
||||
// Remove Grok immediately if present (per spec: "When switching to team mode, Grok is removed")
|
||||
if (state.type === "grokMode") {
|
||||
try { await this.mainChat.apiRemoveMembers(groupId, [state.grokMemberGId]) } catch {}
|
||||
const grokLocalGId = grokGroupMap.get(groupId)
|
||||
grokGroupMap.delete(groupId)
|
||||
if (grokLocalGId) reverseGrokMap.delete(grokLocalGId)
|
||||
}
|
||||
const teamContactId = this.config.teamMembers[0].id // round-robin or first available
|
||||
const member = await this.mainChat.apiAddMember(groupId, teamContactId, "member")
|
||||
this.conversations.set(groupId, {
|
||||
type: "teamPending",
|
||||
teamMemberGId: member.groupMemberId,
|
||||
grokMemberGId: state.type === "grokMode" ? state.grokMemberGId : undefined,
|
||||
history: state.type === "grokMode" ? state.history : undefined,
|
||||
})
|
||||
await this.mainChat.apiSendTextMessage(
|
||||
[T.ChatType.Group, groupId],
|
||||
@@ -358,25 +368,20 @@ class GrokApiClient {
|
||||
|
||||
## 12. One-Way Gate Logic
|
||||
|
||||
Per spec: "When switching to team mode, Grok is removed" and "once the user switches to team mode, /grok command is permanently disabled." Grok removal happens immediately in `activateTeam` (section 10). The one-way gate locks the state after team member engages:
|
||||
|
||||
```typescript
|
||||
async onTeamMemberMessage(groupId: number, state: ConversationState): Promise<void> {
|
||||
if (state.type !== "teamPending") return
|
||||
|
||||
// Remove Grok if present
|
||||
if (state.grokMemberGId) {
|
||||
try { await this.mainChat.apiRemoveMembers(groupId, [state.grokMemberGId]) } catch {}
|
||||
grokGroupMap.delete(groupId)
|
||||
reverseGrokMap.delete(/* grokLocalGroupId */)
|
||||
}
|
||||
|
||||
this.conversations.set(groupId, {type: "teamLocked", teamMemberGId: state.teamMemberGId})
|
||||
}
|
||||
```
|
||||
|
||||
Timeline per spec:
|
||||
1. User sends `/team` → `apiAddMember` → state = `teamPending` (Grok still usable if present)
|
||||
2. Team member sends message → `onTeamMemberMessage` → state = `teamLocked`, Grok removed via `apiRemoveMembers`
|
||||
3. Any `/grok` → reply "You are now in team mode. A team member will reply to your message."
|
||||
1. User sends `/team` → Grok removed immediately (if present) → team member added → state = `teamPending`
|
||||
2. `/grok` in `teamPending` → reply "team mode" (Grok already gone, command disabled)
|
||||
3. Team member sends message → `onTeamMemberMessage` → state = `teamLocked`
|
||||
4. Any subsequent `/grok` → reply "You are now in team mode. A team member will reply to your message."
|
||||
|
||||
## 13. Message Templates (verbatim from spec)
|
||||
|
||||
@@ -417,7 +422,7 @@ function isWeekend(timezone: string): boolean {
|
||||
|
||||
| # | Operation | When | ChatApi Instance | Method | Parameters | Response Type | Error Handling |
|
||||
|---|-----------|------|-----------------|--------|------------|---------------|----------------|
|
||||
| 1 | Init main bot | Startup | mainChat | `bot.run()` (wraps `ChatApi.init`) | dbFilePrefix, profile, addressSettings | `[ChatApi, User, UserContactLink]` | Exit on failure |
|
||||
| 1 | Init main bot | Startup | mainChat | `bot.run()` (wraps `ChatApi.init`) | dbFilePrefix, profile, addressSettings | `[ChatApi, User, UserContactLink \| undefined]` | Exit on failure |
|
||||
| 2 | Init Grok agent | Startup | grokChat | `ChatApi.init(grokDbPrefix)` | dbFilePrefix | `ChatApi` | Exit on failure |
|
||||
| 3 | Get/create Grok user | Startup | grokChat | `apiGetActiveUser()` / `apiCreateActiveUser(profile)` | profile: {displayName: "Grok AI"} | `User` | Exit on failure |
|
||||
| 4 | Start Grok chat | Startup | grokChat | `startChat()` | — | void | Exit on failure |
|
||||
@@ -427,12 +432,12 @@ function isWeekend(timezone: string): boolean {
|
||||
| 8 | First-run: connect | First-run | grokChat | `apiConnectActiveUser(invLink)` | connLink | `ConnReqType` | Exit on failure |
|
||||
| 9 | First-run: wait | First-run | mainChat | `wait("contactConnected", 60000)` | event, timeout | `ChatEvent \| undefined` | Exit on timeout |
|
||||
| 10 | Send msg to customer | Various | mainChat | `apiSendTextMessage([Group, groupId], text)` | chat, text | `AChatItem[]` | Log error |
|
||||
| 11 | Forward to team | welcome→teamQueue, teamQueue msg | mainChat | `apiSendTextMessage([Group, teamGroupId], fwd)` | chat, formatted text | `AChatItem[]` | Log error |
|
||||
| 11 | Forward to team | welcome→teamQueue, teamQueue msg, grokMode msg | mainChat | `apiSendTextMessage([Group, teamGroupId], fwd)` | chat, formatted text | `AChatItem[]` | Log error |
|
||||
| 12 | Invite Grok to group | /grok in teamQueue | mainChat | `apiAddMember(groupId, grokContactId, "member")` | groupId, contactId, role | `GroupMember` | Send error msg, stay in teamQueue |
|
||||
| 13 | Grok joins group | receivedGroupInvitation | grokChat | `apiJoinGroup(groupId)` | groupId | `GroupInfo` | Log error |
|
||||
| 14 | Grok sends response | After Grok API reply | grokChat | `apiSendTextMessage([Group, grokLocalGId], text)` | chat, text | `AChatItem[]` | Send error msg via mainChat |
|
||||
| 15 | Invite team member | /team | mainChat | `apiAddMember(groupId, teamContactId, "member")` | groupId, contactId, role | `GroupMember` | Send error msg to customer |
|
||||
| 16 | Remove Grok | Team member msg in teamPending | mainChat | `apiRemoveMembers(groupId, [grokMemberGId])` | groupId, memberIds | `GroupMember[]` | Ignore (may have left) |
|
||||
| 16 | Remove Grok | /team from grokMode | mainChat | `apiRemoveMembers(groupId, [grokMemberGId])` | groupId, memberIds | `GroupMember[]` | Ignore (may have left) |
|
||||
| 17 | Update bot profile | Startup (via bot.run) | mainChat | `apiUpdateProfile(userId, profile)` | userId, profile with peerType+commands | `UserProfileUpdateSummary` | Log warning |
|
||||
| 18 | Set address settings | Startup (via bot.run) | mainChat | `apiSetAddressSettings(userId, settings)` | userId, {businessAddress, autoAccept, welcomeMessage} | void | Exit on failure |
|
||||
|
||||
@@ -448,7 +453,7 @@ function isWeekend(timezone: string): boolean {
|
||||
| Grok join timeout (30s) | `mainChat.apiSendTextMessage` "Grok unavailable", stay in `teamQueue` |
|
||||
| Customer leaves (`leftMember` where member is customer) | Delete conversation state, delete grokGroupMap entry |
|
||||
| Group deleted | Delete conversation state, delete grokGroupMap entry |
|
||||
| Grok leaves during `teamPending` | Clear `grokMemberGId` from state, keep `teamPending` |
|
||||
| Grok leaves during `grokMode` | Revert to `teamQueue`, delete grokGroupMap entry |
|
||||
| Team member leaves | Revert to `teamQueue` (accumulate messages again) |
|
||||
| Bot removed from group (`deletedMemberUser`) | Delete conversation state |
|
||||
| Grok agent connection lost | Log error; Grok features unavailable until restart |
|
||||
@@ -481,10 +486,10 @@ function isWeekend(timezone: string): boolean {
|
||||
- **Verify:** `/grok` → Grok joins as separate participant → Grok responses appear from Grok profile
|
||||
|
||||
**Phase 4: Team mode + one-way gate**
|
||||
- Implement `activateTeam`: add team member
|
||||
- Implement `onTeamMemberMessage`: detect team msg → lock → remove Grok
|
||||
- Implement `/grok` rejection in `teamLocked`
|
||||
- **Verify:** Full flow including: teamQueue → /grok → grokMode → /team → teamPending → team msg → teamLocked → /grok rejected
|
||||
- Implement `activateTeam`: remove Grok if present, add team member
|
||||
- Implement `onTeamMemberMessage`: detect team msg → lock state
|
||||
- Implement `/grok` rejection in `teamPending` and `teamLocked`
|
||||
- **Verify:** Full flow: teamQueue → /grok → grokMode → /team → Grok removed + teamPending → /grok rejected → team msg → teamLocked
|
||||
|
||||
**Phase 5: Polish + first-run**
|
||||
- Implement `--first-run` auto-contact establishment
|
||||
@@ -533,9 +538,9 @@ npx ts-node src/index.ts \
|
||||
2. Send question → verify forwarded to team group with `[CustomerName #groupId]` prefix, queue reply received
|
||||
3. Send `/grok` → verify Grok joins as separate participant, responses appear from "Grok AI" profile
|
||||
4. Send text in grokMode → verify Grok response + forwarded to team
|
||||
5. Send `/team` → verify team member added, team added message
|
||||
6. Send team member message → verify Grok removed, state locked
|
||||
7. Send `/grok` after lock → verify "team mode" reply
|
||||
5. Send `/team` → verify Grok removed, team member added, team added message
|
||||
6. Send `/grok` after `/team` (before team member message) → verify "team mode" reply
|
||||
7. Send team member message → verify state locked, `/grok` still rejected
|
||||
8. Test weekend: set timezone to weekend timezone → verify "48 hours" in messages
|
||||
9. Customer disconnects → verify state cleanup
|
||||
10. Grok API failure → verify error message, graceful fallback to teamQueue
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
A SimpleX Chat bot that monitors public groups, summarizes conversations using
|
||||
Grok LLM, moderates content, and forwards important messages to a private
|
||||
staff group.
|
||||
|
||||
Core Features
|
||||
|
||||
1. Message Summarization
|
||||
- Periodically summarizes public group messages using Grok API
|
||||
- Posts summaries to the group on a configurable schedule (e.g. daily/hourly)
|
||||
- Summaries capture key topics, decisions, and action items
|
||||
|
||||
2. Moderation
|
||||
- Detects spam, abuse, and policy violations using Grok
|
||||
- Configurable actions per severity: flag-only, auto-delete, or remove member
|
||||
- All moderation events are forwarded to the staff group for visibility
|
||||
|
||||
3. Important Message Forwarding
|
||||
- Grok classifies messages by importance (urgency, issues, support requests)
|
||||
- Forwards important messages to a designated private staff group
|
||||
- Includes context: sender, group, timestamp, and reason for flagging
|
||||
|
||||
Configuration
|
||||
|
||||
- GROK_API_KEY — Grok API credentials
|
||||
- PUBLIC_GROUPS — list of monitored public groups
|
||||
- STAFF_GROUP — private group for forwarded alerts
|
||||
- SUMMARY_INTERVAL — how often summaries are generated
|
||||
- MODERATION_RULES — content policy and action thresholds
|
||||
|
||||
Non-Goals
|
||||
|
||||
- No interactive Q&A or general chatbot behavior in groups
|
||||
- No direct user communication from the bot (all escalation goes to staff
|
||||
group)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user