# Feishu Bot Integration Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Enable Feishu (飞书) bot integration as a standalone **Plugin** that users can manage via the system's "Plugins" menu. This allows users to bind bots, interact with RAG, and access assessment features in a modular, plug-and-play fashion.

**Architecture:** Implement the integration as a pluggable `FeishuModule`. It acts as a bridge between Feishu Open Platform and AuraK's core services. The plugin is managed through the new `/plugins` workspace, isolating its UI and backend logic from the core system.

**Tech Stack:**
- NestJS (existing)
- Feishu Open Platform APIs (im/v1/messages, auth/v3/tenant_access_token)
- Event subscription (Webhooks)
- Existing: ChatService, AssessmentService, JWT Auth

---

## Architecture Overview

```
┌─────────────┐      ┌──────────────┐      ┌─────────────┐
│  Feishu User│─────▶│ Feishu Server│─────▶│  AuraK API  │
│  (User A)   │◀─────│   Webhook    │◀─────│  /feishu/*  │
└─────────────┘      └──────────────┘      └─────────────┘
                                                      │
                                                      ▼
                                              ┌─────────────┐
                                              │ FeishuModule│
                                              │ - Bot Entity│
                                              │ - Message   │
                                              │ - Events    │
                                              └─────────────┘
                                                      │
                              ┌───────────────────────┼───────────────────────┐
                              ▼                       ▼                       ▼
                      ┌─────────────┐        ┌─────────────┐        ┌─────────────┐
                      │ ChatService │        │AssessmentSvc│        │ PluginsSvc  │
                      │  (RAG Q&A)  │        │ (评测对话)   │        │ (插件状态管理)│
                      └─────────────┘        └─────────────┘        └─────────────┘
```

**Plugin Isolation Strategy:**
- **Backend**: `FeishuModule` is a standalone NestJS module. It handles its own database entities and webhook logic.
- **Frontend**: Integrated as a sub-view within `/plugins`. When the Feishu plugin is "enabled", its configuration UI is rendered.
- **Decoupling**: Communicates with core services via standard service calls or an event-subscriber pattern.

---

## File Structure


### New Files to Create

```
server/src/
├── feishu/
│   ├── feishu.module.ts              # Module definition
│   ├── feishu.controller.ts          # Webhook endpoints
│   ├── feishu.service.ts             # Business logic
│   ├── feishu.gateway.ts             # Event handling (optional)
│   ├── entities/
│   │   └── feishu-bot.entity.ts      # Bot configuration entity
│   ├── dto/
│   │   ├── create-bot.dto.ts         # Create bot DTO
│   │   ├── bind-bot.dto.ts           # Bind bot DTO
│   │   └── feishu-webhook.dto.ts    # Webhook event DTO
│   └── interfaces/
│       └── feishu.interface.ts       # TypeScript interfaces
```

### Existing Files to Modify

```
server/src/
├── app.module.ts                      # Import FeishuModule
├── user/
│   └── user.entity.ts                # Add one-to-many relation to FeishuBot
├── user/user.service.ts               # Add methods to get/set Feishu binding
```

---

## Chunk 1: Entity and DTO Definitions

### Task 1.1: Create FeishuBot Entity

**Files:**
- Create: `server/src/feishu/entities/feishu-bot.entity.ts`

- [ ] **Step 1: Create the FeishuBot entity file**

```typescript
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  ManyToOne,
  JoinColumn,
} from 'typeorm';
import { User } from '../../user/user.entity';

@Entity('feishu_bots')
export class FeishuBot {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ name: 'user_id' })
  userId: string; // Plugin manages its own relationship to the core User

  @ManyToOne(() => User)
  @JoinColumn({ name: 'user_id' })
  user: User;

  @Column({ name: 'app_id', length: 64 })
  appId: string;
// ... (rest of the fields as defined previously)
}
```

- [ ] **Step 2: Decoupled Relation**
Instead of modifying the core `User` entity directly, the `FeishuBot` entity maintains its own reference to `User`. This keeps the core system clean and allows the plugin to be purely optional.


- [ ] **Step 3: Create DTOs**

Create: `server/src/feishu/dto/create-bot.dto.ts`
```typescript
import { IsString, IsNotEmpty, IsOptional, IsBoolean } from 'class-validator';

export class CreateFeishuBotDto {
  @IsString()
  @IsNotEmpty()
  appId: string;

  @IsString()
  @IsNotEmpty()
  appSecret: string;

  @IsString()
  @IsOptional()
  verificationToken?: string;

  @IsString()
  @IsOptional()
  encryptKey?: string;

  @IsString()
  @IsOptional()
  botName?: string;

  @IsBoolean()
  @IsOptional()
  enabled?: boolean;
}
```

Create: `server/src/feishu/dto/bind-bot.dto.ts`
```typescript
import { IsString, IsNotEmpty, IsUUID } from 'class-validator';

export class BindFeishuBotDto {
  @IsUUID()
  @IsNotEmpty()
  botId: string;

  @IsString()
  @IsNotEmpty()
  verificationCode?: string;  // Optional: 用于验证绑定关系
}
```

---

## Chunk 2: Core Service Implementation

### Task 2.1: Create Feishu Service

**Files:**
- Create: `server/src/feishu/feishu.service.ts`

- [ ] **Step 1: Implement FeishuService with token management and message sending**

```typescript
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { FeishuBot } from './entities/feishu-bot.entity';
import { CreateFeishuBotDto } from './dto/create-bot.dto';

@Injectable()
export class FeishuService {
  private readonly logger = new Logger(FeishuService.name);
  private readonly feishuApiBase = 'https://open.feishu.cn/open-apis';

  constructor(
    @InjectRepository(FeishuBot)
    private botRepository: Repository<FeishuBot>,
    private httpService: HttpService,
    private configService: ConfigService,
  ) {}

  /**
   * Create or update a Feishu bot for a user
   */
  async createBot(userId: string, dto: CreateFeishuBotDto): Promise<FeishuBot> {
    // Check if bot already exists for this user with same appId
    const existing = await this.botRepository.findOne({
      where: { userId, appId: dto.appId },
    });

    if (existing) {
      // Update existing bot
      Object.assign(existing, dto);
      return this.botRepository.save(existing);
    }

    // Create new bot
    const bot = this.botRepository.create({
      userId,
      ...dto,
    });
    return this.botRepository.save(bot);
  }

  /**
   * Get all bots for a user
   */
  async getUserBots(userId: string): Promise<FeishuBot[]> {
    return this.botRepository.find({ where: { userId } });
  }

  /**
   * Get bot by ID
   */
  async getBotById(botId: string): Promise<FeishuBot | null> {
    return this.botRepository.findOne({ where: { id: botId } });
  }

  /**
   * Get bot by appId
   */
  async getBotByAppId(appId: string): Promise<FeishuBot | null> {
    return this.botRepository.findOne({ where: { appId } });
  }

  /**
   * Get or refresh tenant_access_token
   */
  async getValidToken(bot: FeishuBot): Promise<string> {
    // Check if token is still valid (expire in 2 hours, refresh at 1.5 hours)
    if (
      bot.tokenExpiresAt &&
      bot.tenantAccessToken &&
      new Date(bot.tokenExpiresAt) > new Date(Date.now() + 30 * 60 * 1000)
    ) {
      return bot.tenantAccessToken;
    }

    // Refresh token
    const response = await this.httpService
      .post(`${this.feishuApiBase}/auth/v3/tenant_access_token/internal`, {
        app_id: bot.appId,
        app_secret: bot.appSecret,
      })
      .toPromise();

    if (response.data.code !== 0) {
      throw new Error(`Failed to get token: ${response.data.msg}`);
    }

    const { tenant_access_token, expire } = response.data;

    // Update bot with new token
    bot.tenantAccessToken = tenant_access_token;
    bot.tokenExpiresAt = new Date(Date.now() + expire * 1000);
    await this.botRepository.save(bot);

    return tenant_access_token;
  }

  /**
   * Send message to Feishu user
   */
  async sendMessage(
    bot: FeishuBot,
    receiveIdType: 'open_id' | 'user_id' | 'union_id' | 'chat_id',
    receiveId: string,
    messageType: 'text' | 'interactive' | 'post',
    content: any,
  ): Promise<string> {
    const token = await this.getValidToken(bot);

    const response = await this.httpService
      .post(
        `${this.feishuApiBase}/im/v1/messages?receive_id_type=${receiveIdType}`,
        {
          receive_id: receiveId,
          msg_type: messageType,
          content: JSON.stringify(content),
        },
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
      )
      .toPromise();

    if (response.data.code !== 0) {
      throw new Error(`Failed to send message: ${response.data.msg}`);
    }

    return response.data.data.message_id;
  }

  /**
   * Send text message (convenience method)
   */
  async sendTextMessage(
    bot: FeishuBot,
    receiveIdType: 'open_id' | 'user_id' | 'chat_id',
    receiveId: string,
    text: string,
  ): Promise<string> {
    return this.sendMessage(bot, receiveIdType, receiveId, 'text', { text });
  }

  /**
   * Reply to a message
   */
  async replyMessage(
    bot: FeishuBot,
    messageId: string,
    messageType: 'text' | 'interactive' | 'post',
    content: any,
  ): Promise<string> {
    const token = await this.getValidToken(bot);

    const response = await this.httpService
      .post(
        `${this.feishuApiBase}/im/v1/messages/${messageId}/reply`,
        {
          msg_type: messageType,
          content: JSON.stringify(content),
        },
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
      )
      .toPromise();

    if (response.data.code !== 0) {
      throw new Error(`Failed to reply message: ${response.data.msg}`);
    }

    return response.data.data.message_id;
  }

  /**
   * Upload image for sending
   */
  async uploadImage(
    bot: FeishuBot,
    imageType: 'message' | 'avatar',
    image: Buffer,
    imageName: string,
  ): Promise<string> {
    const token = await this.getValidToken(bot);

    const FormData = require('form-data');
    const form = new FormData();
    form.append('image_type', imageType);
    form.append('image', image, { filename: imageName });

    const response = await this.httpService
      .post(`${this.feishuApiBase}/im/v1/images`, form, {
        headers: {
          Authorization: `Bearer ${token}`,
          ...form.getHeaders(),
        },
      })
      .toPromise();

    if (response.data.code !== 0) {
      throw new Error(`Failed to upload image: ${response.data.msg}`);
    }

    return response.data.data.image_key;
  }

  /**
   * Delete bot
   */
  async deleteBot(botId: string): Promise<void> {
    await this.botRepository.delete(botId);
  }

  /**
   * Enable/disable bot
   */
  async setBotEnabled(botId: string, enabled: boolean): Promise<FeishuBot> {
    const bot = await this.botRepository.findOne({ where: { id: botId } });
    if (!bot) {
      throw new Error('Bot not found');
    }
    bot.enabled = enabled;
    return this.botRepository.save(bot);
  }
}
```

---

## Chunk 3: Controller and Webhook Endpoints

### Task 3.1: Create Feishu Controller

**Files:**
- Create: `server/src/feishu/feishu.controller.ts`

- [ ] **Step 1: Implement webhook endpoints**

```typescript
import {
  Controller,
  Post,
  Get,
  Delete,
  Body,
  Param,
  Query,
  Headers,
  UseGuards,
  Request,
  RawBodyRequest,
  Req,
} from '@nestjs/common';
import { Request as ExpressRequest } from 'express';
import { FeishuService } from './feishu.service';
import { CreateFeishuBotDto } from './dto/create-bot.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { CreateSignatureDto, VerifyWebhookDto } from './dto/webhook.dto';

@Controller('feishu')
export class FeishuController {
  constructor(private readonly feishuService: FeishuService) {}

  /**
   * GET /feishu/bots - List user's bots
   * Requires JWT auth
   */
  @Get('bots')
  @UseGuards(JwtAuthGuard)
  async listBots(@Request() req) {
    const bots = await this.feishuService.getUserBots(req.user.id);
    // Mask sensitive data
    return bots.map((bot) => ({
      id: bot.id,
      appId: bot.appId,
      botName: bot.botName,
      enabled: bot.enabled,
      isDefault: bot.isDefault,
      createdAt: bot.createdAt,
    }));
  }

  /**
   * POST /feishu/bots - Create a new bot
   * Requires JWT auth
   */
  @Post('bots')
  @UseGuards(JwtAuthGuard)
  async createBot(@Request() req, @Body() dto: CreateFeishuBotDto) {
    const bot = await this.feishuService.createBot(req.user.id, dto);
    return {
      id: bot.id,
      appId: bot.appId,
      botName: bot.botName,
      enabled: bot.enabled,
    };
  }

  /**
   * DELETE /feishu/bots/:id - Delete a bot
   * Requires JWT auth
   */
  @Delete('bots/:id')
  @UseGuards(JwtAuthGuard)
  async deleteBot(@Request() req, @Param('id') botId: string) {
    await this.feishuService.deleteBot(botId);
    return { success: true };
  }

  /**
   * POST /feishu/webhook - Feishu webhook endpoint
   * Public endpoint - no auth required (uses verification token)
   */
  @Post('webhook')
  async handleWebhook(
    @Body() body: any,
    @Headers('x-xsign') xSign?: string,
    @Headers('x-timESTAMP') xTimestamp?: string,
  ) {
    this.logger.log(`Received webhook: ${JSON.stringify(body)}`);

    const { type, schema, event } = body;

    // Handle URL verification (飞书首次配置时验证)
    if (type === 'url_verification') {
      return {
        challenge: body.challenge,
      };
    }

    // Handle event callback
    if (type === 'event_callback') {
      const { event_type, token } = body;

      // Verify token
      if (token !== body?.verify_token) {
        this.logger.warn('Webhook token verification failed');
        return { success: false };
      }

      // Handle different event types
      switch (event_type) {
        case 'im.message.p2p_msg_received':
          // Handle private message
          await this.handleP2PMsg(event);
          break;

        case 'im.message.group_at_msg_received':
          // Handle group @message
          await this.handleGroupMsg(event);
          break;

        default:
          this.logger.log(`Unhandled event type: ${event_type}`);
      }
    }

    return { success: true };
  }

  /**
   * Handle private message
   */
  private async handleP2PMsg(event: any) {
    const { message } = event;
    const { message_id, chat_id, sender, message_type, body } = message;

    // Get message content
    const content = JSON.parse(message.content || '{}');
    const text = content.text || '';

    // Find bot by app_id (from chat or event)
    const openId = sender?.id?.open_id;
    if (!openId) {
      this.logger.warn('No sender open_id found');
      return;
    }

    // Find user's bot
    const bot = await this.feishuService.getBotByAppId(sender?.sender_id?.app_id);
    if (!bot || !bot.enabled) {
      this.logger.warn('Bot not found or disabled');
      return;
    }

    // Process message through RAG/Chat service
    await this.processMessage(bot, openId, message_id, text);
  }

  /**
   * Handle group message (@bot)
   */
  private async handleGroupMsg(event: any) {
    const { message } = event;
    const { message_id, chat_id, sender, message_type, content } = message;

    // Check if bot was mentioned
    const msgContent = JSON.parse(content || '{}');
    // Group messages often require specific handling

    // Similar to P2P but with chat_id context
    await this.handleP2PMsg(event);
  }

  /**
   * Process message through ChatService
   */
  private async processMessage(
    bot: any,
    openId: string,
    messageId: string,
    text: string,
  ) {
    // TODO: Integrate with ChatService
    // This will be implemented in Chunk 5

    // For now, echo back (placeholder)
    try {
      await this.feishuService.sendTextMessage(
        bot,
        'open_id',
        openId,
        `Received: ${text}`,
      );
    } catch (error) {
      this.logger.error('Failed to send response', error);
    }
  }
}
```

- [ ] **Step 2: Create webhook DTOs**

Create: `server/src/feishu/dto/webhook.dto.ts`
```typescript
import { IsString, IsOptional } from 'class-validator';

export class CreateSignatureDto {
  @IsString()
  @IsOptional()
  timestamp?: string;

  @IsString()
  @IsOptional()
  nonce?: string;
}

export class VerifyWebhookDto {
  @IsString()
  token: string;

  @IsString()
  @IsOptional()
  challenge?: string;
}
```

---

## Chunk 4: Plugin Registration

### Task 4.1: Register Feishu Plugin
**Note:** This module acts as an optional extension to the AuraK ecosystem.

**Files:**
- Create: `server/src/feishu/feishu.module.ts`
- Modify: `server/src/app.module.ts`

- [ ] **Step 1: Create FeishuModule with isolated exports**


```typescript
import { Module, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { HttpModule } from '@nestjs/axios';
import { FeishuController } from './feishu.controller';
import { FeishuService } from './feishu.service';
import { FeishuBot } from './entities/feishu-bot.entity';
import { ChatModule } from '../chat/chat.module';
import { UserModule } from '../user/user.module';

@Module({
  imports: [
    TypeOrmModule.forFeature([FeishuBot]),
    HttpModule,
    forwardRef(() => ChatModule),
    forwardRef(() => UserModule),
  ],
  controllers: [FeishuController],
  providers: [FeishuService],
  exports: [FeishuService, TypeOrmModule],
})
export class FeishuModule {}
```

- [ ] **Step 2: Add FeishuModule to AppModule**

Modify: `server/src/app.module.ts`

Add import:
```typescript
import { FeishuModule } from './feishu/feishu.module';
```

Add to imports array:
```typescript
FeishuModule,
```

---

## Chunk 5: Integration with ChatService

### Task 5.1: Connect Feishu messages to ChatService

**Files:**
- Modify: `server/src/feishu/feishu.controller.ts`
- Modify: `server/src/feishu/feishu.service.ts`

- [ ] **Step 1: Extend FeishuService to handle chat integration**

Add to FeishuService:
```typescript
  // Import these
  import { ChatService } from '../chat/chat.service';
  import { ModelConfigService } from '../model-config/model-config.service';
  import { TenantService } from '../tenant/tenant.service';
  import { UserService } from '../user/user.service';
  import { ModelType } from '../types';

  // Add to constructor
  constructor(
    // ... existing
    private chatService: ChatService,
    private modelConfigService: ModelConfigService,
    private tenantService: TenantService,
    private userService: UserService,
  ) {}

  /**
   * Process chat message through RAG
   */
  async processChatMessage(
    bot: FeishuBot,
    openId: string,
    messageId: string,
    userMessage: string,
  ): Promise<void> {
    // Get user by Feishu open_id mapping (future: map table)
    // For now, use userId from bot
    const userId = bot.userId;
    
    // Get user's tenant
    const user = await this.userService.findById(userId);
    const tenantId = user?.tenantId || 'default';

    // Get user's LLM config
    const llmModel = await this.modelConfigService.findDefaultByType(
      tenantId,
      ModelType.LLM,
    );

    if (!llmModel) {
      await this.sendTextMessage(bot, 'open_id', openId, '请先配置 LLM 模型');
      return;
    }

    // Send "thinking" message
    await this.sendTextMessage(bot, 'open_id', openId, '正在思考...');

    // Stream chat response
    const stream = this.chatService.streamChat(
      userMessage,
      [],  // No history for now (future: persist per openId)
      userId,
      llmModel as any,
      user?.userSetting?.language || 'zh',
      undefined, // selectedEmbeddingId
      undefined, // selectedGroups
      undefined, // selectedFiles
      undefined, // historyId
      false, // enableRerank
      undefined, // selectedRerankId
      undefined, // temperature
      undefined, // maxTokens
      10, // topK
      0.7, // similarityThreshold
      undefined, // rerankSimilarityThreshold
      undefined, // enableQueryExpansion
      undefined, // enableHyDE
      tenantId,
    );

    let fullResponse = '';
    for await (const chunk of stream) {
      if (chunk.type === 'content') {
        fullResponse += chunk.data;
        // Could send incrementally, but Feishu prefers complete messages
      }
    }

    // Send final response
    // Truncate if too long (Feishu has limits)
    const maxLength = 5000;
    const finalMessage = fullResponse.length > maxLength
      ? fullResponse.substring(0, maxLength) + '...(内容过长)'
      : fullResponse;

    await this.sendTextMessage(bot, 'open_id', openId, finalMessage);
  }
```

- [ ] **Step 2: Update controller to use chat integration**

Modify the `processMessage` method in FeishuController to call `feishuService.processChatMessage()`:

```typescript
  private async processMessage(
    bot: any,
    openId: string,
    messageId: string,
    text: string,
  ) {
    try {
      await this.feishuService.processChatMessage(bot, openId, messageId, text);
    } catch (error) {
      this.logger.error('Failed to process message', error);
      try {
        await this.feishuService.sendTextMessage(
          bot,
          'open_id',
          openId,
          '抱歉，处理消息时发生错误，请稍后重试。',
        );
      } catch (sendError) {
        this.logger.error('Failed to send error message', sendError);
      }
    }
  }
```

---

## Chunk 6: Frontend Integration (Optional)

### Task 6.1: Add Feishu sub-view to Plugins
**Files:**
- Create: `web/src/pages/Plugins/FeishuPlugin.tsx`
- Modify: `web/src/components/views/PluginsView.tsx`
- Modify: `web/src/services/api.ts`

- [ ] **Step 1: Create the Plugin Configuration UI**
This view should match the design of other plugin cards in the /plugins page but provide a detailed setup guide and form for:
- App ID / App Secret
- Webhook URL (Read-only generated URL)
- Verification Token & Encrypt Key

- [ ] **Step 2: Register the Plugin in PluginsView**
Modify the main plugins listing to include "Feishu Bot" as an available (or installed) plugin.

Modify: `web/src/services/api.ts`
```typescript
// Add Feishu API calls
export const feishuApi = {
  listBots: () => api.get('/feishu/bots'),
  createBot: (data: CreateBotDto) => api.post('/feishu/bots', data),
  deleteBot: (botId: string) => api.delete(`/feishu/bots/${botId}`),
};
```

- [ ] **Step 3: Implementation of PluginsView.tsx**
If `web/src/components/views/PluginsView.tsx` does not exist, create a generic plugin management layout that can host the Feishu configuration.

**Layout Requirements:**
- Grid of available plugins.
- Status toggle (Enabled/Disabled).
- Detail/Configuration view for active plugins.


## Testing Strategy

### Unit Tests

- `feishu.service.spec.ts` - Test token refresh, message sending
- `feishu.controller.spec.ts` - Test webhook endpoints

### Integration Tests

- Test full flow: Feishu message → Webhook → ChatService → Response

### Manual Testing

1. Create Feishu app in 开放平台
2. Configure webhook URL (use ngrok for local)
3. Subscribe to message events
4. Send message to bot
5. Verify RAG response


---

## Security Considerations

1. **Token Storage**: Encrypt `app_secret` and `encrypt_key` before storing in DB.
2. **Webhook Verification**:
   - Verify `verify_token` for simplicity.
   - **Recommended**: Validate `X-Lark-Signature` using `encrypt_key` to ensure authenticity.

3. **Rate Limiting**: Implement a message queue (e.g., BullMQ) for outbound messages to respect Feishu's global and per-bot rate limits.
4. **User Privacy**: Implement an opt-in flow for group chats to ensure the bot only processes messages when explicitly allowed or mentioned.

---

## Advanced Optimizations (Recommended)

### 1. Webhook Performance & Reliability
- **Immediate Response**: Feishu requires a response within 3 seconds. The RAG process can take 10s+. 
- **Optimization**: The `handleWebhook` should only validate the request and push the event to an internal queue, returning `200 OK` immediately. A background worker then processes the RAG logic and sends the response.
- **Deduplication**: Use the `event_id` in the webhook payload to ignore duplicate retries from Feishu.

### 2. UX: Managed "Thinking" State
- **Simulated Streaming**: Since Feishu doesn't support SSE, send an initial "Thinking..." message and use the `PATCH /im/v1/messages/:message_id` API to update the message content every few seconds as chunks arrive.
- **Interactive Cards**: Use [Message Cards](https://open.feishu.cn/document/common-capabilities/message-card/message-card-overview) instead of plain text for:
  - Showing search citations with clickable links.
  - Providing "Regenerate" or "Clear Context" buttons.
  - Displaying assessment results with formatting (tables/charts).

### 3. Context & History Management
- **OpenID Mapping**: Maintain a mapping between Feishu `open_id` and AuraK `userId` to persist chat history across different devices/platforms.
- **Thread Support**: Use Feishu's `root_id` and `parent_id` to allow users to ask follow-up questions within a thread, keeping the UI clean.

### 4. Multi-modal Support
- **File Ingestion**: Support `im.message.file_received` events. If a user sends a PDF/Docx to the bot, automatically import it into a "Feishu Uploads" group for immediate RAG context.
- **Image Analysis**: Use the `VisionService` to handle images sent via Feishu.


## Future Enhancements

1. **Assessment Integration**: Bind assessment sessions to Feishu conversations using interactive card forms.
2. **Rich Responses**: Use Feishu interactive cards for better visual presentation.
3. **Multi-bot Support**: Users can have multiple bots for different specialized tasks.
4. **Group Chats**: Support bot in group chats with specific @mention logic and moderation.
5. **Voice Messages**: Handle voice message transcription via Feishu's audio-to-text API for accessibility.


---

## Dependencies

- `@nestjs/axios` - For HTTP requests to Feishu API
- `form-data` - For file/image uploads
- Optional: `crypto` (built-in) - For signature verification

---

## Reference Links

- [飞书开放平台 - 机器人文档](https://open.feishu.cn/document/faq/bot)
- [飞书事件订阅](https://open.feishu.cn/document/server-docs/event-subscription-guide/overview)
- [消息发送 API](https://open.feishu.cn/document/server-docs/im-v1/message-content-description)
- [获取 tenant_access_token](https://open.feishu.cn/document/server-docs/authentication-management/access-token)

---

> **Plan created:** 2026-03-16
> **Based on:** Feishu integration analysis
