Enhance Slack event handling by adding hourglass reaction on app mentions and refactoring reaction management in slack-agent-task. Update CLAUDE.md and global.mdc with new guidelines for testing and development workflows. Adjust .gitignore for rerank package to ensure proper directory exclusion.

This commit is contained in:
dal 2025-07-18 21:40:20 -06:00
parent f9786c75c8
commit ba04450469
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
7 changed files with 309 additions and 568 deletions

View File

@ -7,6 +7,8 @@ alwaysApply: true
This file provides guidance to Claude Code when working with code in this monorepo.
**Note**: Many packages and apps have their own CLAUDE.md files with specific implementation details and patterns. Always check for a local CLAUDE.md when working in a specific directory.
## Monorepo Structure
This is a pnpm-based monorepo using Turborepo with the following structure:
@ -33,6 +35,7 @@ This is a pnpm-based monorepo using Turborepo with the following structure:
- `packages/web-tools` - Web scraping and research tools
- `packages/slack` - Standalone Slack integration (OAuth, messaging, channels)
- `packages/supabase` - Supabase setup and configuration
- `packages/sandbox` - Sandboxed code execution using Daytona SDK
## Development Workflow
@ -42,6 +45,8 @@ When writing code, follow this workflow to ensure code quality:
- Create small, focused functions with single responsibilities
- Design functions to be easily testable with clear inputs/outputs
- Use dependency injection for external dependencies
- **IMPORTANT: Write functional, composable code - avoid classes**
- All features should be composed of testable functions
- Follow existing patterns in the codebase
### 2. Build Features by Composing Functions
@ -75,12 +80,18 @@ pnpm run check:fix packages/ai
```
### 5. Run Tests with Vitest
```bash
# Run all tests
pnpm run test
# Run tests for specific package
turbo run test --filter=@buster/ai
**Important**: Always run unit tests before completing any task to ensure code changes don't break existing functionality.
```bash
# Run unit tests (always run these when working locally)
turbo run test:unit
# Run unit tests for specific package
turbo run test:unit --filter=@buster/ai
# Run integration tests ONLY for specific features/packages you're working on
turbo run test:integration --filter=@buster/database
# Run specific test file
pnpm run test path/to/file.test.ts
@ -89,6 +100,28 @@ pnpm run test path/to/file.test.ts
pnpm run test:watch
```
### 6. Pre-Completion Checklist
**IMPORTANT: Before finishing any task or creating PRs, always run:**
```bash
# 1. Run unit tests for the entire monorepo
turbo run test:unit
# 2. Build the entire monorepo to ensure everything compiles
turbo run build
# 3. Run linting for the entire monorepo
turbo run lint
```
**Key Testing Guidelines:**
- **Always run unit tests, build, and lint** when working locally before considering a task complete
- **Unit tests** should be run for the entire monorepo to catch any breaking changes
- **Build** must pass for the entire monorepo to ensure type safety
- **Integration tests** should only be run for specific packages/features you're working on (NOT the entire monorepo)
- **Fix all failing tests, build errors, and lint errors** before completing any task
- **Heavily bias toward unit tests** - they are faster and cheaper to run
- **Mock everything you can** in unit tests for isolation and speed
## Code Quality Standards
### TypeScript Configuration
@ -98,6 +131,12 @@ pnpm run test:watch
- **No implicit returns** - All code paths must return
- **Consistent file casing** - Enforced by TypeScript
### Type Safety and Zod Best Practices
- We care deeply about type safety and we use Zod schemas and then export them as types
- We prefer using type abstractions over `.parse()` method calls
- Always export Zod schemas as TypeScript types to leverage static type checking
- Avoid runtime type checking when compile-time type checks are sufficient
### Biome Rules (Key Enforcements)
- **`useImportType: "warn"`** - Use type-only imports when possible
- **`noExplicitAny: "error"`** - Never use `any` type
@ -106,511 +145,75 @@ pnpm run test:watch
- **`noConsoleLog: "warn"`** - Avoid console.log in production
- **`useNodejsImportProtocol: "error"`** - Use `node:` prefix for Node.js imports
### Testing Practices
### Logging Guidelines
- **Never use `console.log`**
- **Use appropriate console methods**:
- `console.info` for general information
- `console.warn` for warning messages
- `console.error` for error messages
#### Test File Naming & Location
- **Unit tests**: `filename.test.ts` (alongside the source file)
- **Integration tests**: `filename.int.test.ts` (alongside the source file)
- Never separate tests into their own folders - keep them with the code they test
## Error Handling and Logging Philosophy
- We care deeply about error handling and logging
- Key principles for error management:
- Catch errors effectively and thoughtfully
- Consider the state errors put the system into
- Implement comprehensive unit tests for error scenarios
- Log errors strategically for effective debugging
- Avoid over-logging while ensuring sufficient context for troubleshooting
#### Testing Strategy
1. **Prioritize mocking** for unit tests after understanding API/DB structure
2. **Integration tests** should focus on single connection confirmations
3. **Mock external dependencies** appropriately
4. **Use descriptive test names** that explain the behavior
5. **Write tests alongside implementation** for better coverage
## Hono API Development Guidelines
#### Example Test Structure
### API Structure and Organization
- **Version-based organization** - APIs are organized under `/api/v2/` directory
- **Feature-based folders** - Each feature gets its own folder (e.g., `chats/`, `security/`)
- **Separate handler files** - Each endpoint handler must be in its own file
- **Functional handlers** - All handlers should be pure functions that accept request data and return response data
### Request/Response Type Safety
- **Use shared types** - All request and response types must be defined in `@buster/server-shared`
- **Zod schemas** - Define schemas in server-shared and export both the schema and inferred types
- **zValidator middleware** - Always use `zValidator` from `@hono/zod-validator` for request validation
- **Type imports** - Import types from server-shared packages for consistency
### Handler Pattern
```typescript
// user-service.ts
export function getUserById(id: string) { /* ... */ }
// Handler file (e.g., get-workspace-settings.ts)
import type { GetWorkspaceSettingsResponse } from '@buster/server-shared/security';
import type { User } from '@buster/database';
// user-service.test.ts (same directory)
import { describe, it, expect, vi } from 'vitest';
import { getUserById } from './user-service';
describe('getUserById', () => {
it('should return user when found', async () => {
// Test implementation
});
});
// user-service.int.test.ts (integration test)
import { describe, it, expect } from 'vitest';
import { getUserById } from './user-service';
describe('getUserById integration', () => {
it('should connect to database successfully', async () => {
// Single connection test
});
});
```
## Code Style Preferences
### Type Safety
- **Zod-First Approach** - Use Zod schemas as the single source of truth for both validation and types
- **Use `z.infer<typeof schema>` for types** - Prefer inferred types over separate interfaces
- **Never use `any`** - Biome enforces this with `noExplicitAny: "error"`
- **Avoid `unknown` unless necessary** - Prefer specific types or properly typed unions
- **Handle null/undefined explicitly** - TypeScript strict mode enforces this
- **Safe array access** - Use validation helpers when needed
- **Type-only imports** - Use `import type` for better performance
#### Zod-First Type Safety Pattern
```typescript
// ✅ Good: Zod schema as single source of truth
const userSchema = z.object({
id: z.string().min(1),
email: z.string().email(),
role: z.enum(['admin', 'user']),
});
type User = z.infer<typeof userSchema>; // Inferred type
// ✅ Good: Safe runtime validation
const validatedUser = userSchema.parse(rawData);
// ✅ Good: Safe array access when needed
import { validateArrayAccess } from '@buster/ai/utils/validation-helpers';
const firstItem = validateArrayAccess(array, 0, 'user processing');
// ❌ Avoid: Separate interface + unsafe access
interface User {
id: string;
email: string;
}
const user = rawData as User; // Unsafe type assertion
const firstItem = array[0]!; // Non-null assertion not allowed
```
### Import Organization
- Use **type-only imports** when importing only types: `import type { SomeType } from './types'`
- Biome automatically organizes imports with `pnpm run check:fix`
- Use Node.js protocol: `import { readFile } from 'node:fs'`
- Follow path aliases defined in each package's tsconfig.json
### String Handling
- **Prefer template literals** over string concatenation for better readability
- Use template literals for multi-line strings and string interpolation
#### String Handling Patterns
```typescript
// ✅ Good: Template literals
const message = `User ${userId} not found`;
const multiLine = `This is a
multi-line string`;
const path = `${baseUrl}/api/users/${userId}`;
// ❌ Avoid: String concatenation
const message = 'User ' + userId + ' not found';
const path = baseUrl + '/api/users/' + userId;
```
### Error Handling
- **Always use try-catch blocks** for async operations and external calls
- **Never use `any` in catch blocks** - Biome enforces this
- **Validate external data** with Zod schemas before processing
- **Provide meaningful error messages** with context for debugging
- **Handle errors at appropriate levels** - don't let errors bubble up uncaught
- **Use structured logging** for error tracking
#### Error Handling Patterns
```typescript
// ✅ Good: Comprehensive error handling
async function processUserData(userId: string) {
try {
const user = await getUserById(userId);
if (!user) {
throw new Error(`User not found: ${userId}`);
}
const validatedData = UserSchema.parse(user);
return await processData(validatedData);
} catch (error) {
logger.error('Failed to process user data', {
userId,
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
throw new Error(`User data processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
export async function getWorkspaceSettingsHandler(
user: User
): Promise<GetWorkspaceSettingsResponse> {
// Implementation
}
// ✅ Good: Database operations with error handling
async function createResource(data: CreateResourceInput) {
try {
const validatedData = CreateResourceSchema.parse(data);
return await db.transaction(async (tx) => {
const resource = await tx.insert(resources).values(validatedData).returning();
await tx.insert(resourceAudit).values({
resourceId: resource[0].id,
action: 'created',
createdAt: new Date()
});
return resource[0];
});
} catch (error) {
if (error instanceof ZodError) {
throw new Error(`Invalid resource data: ${error.errors.map(e => e.message).join(', ')}`);
}
logger.error('Database error creating resource', { data, error });
throw new Error('Failed to create resource');
}
}
// ❌ Avoid: Unhandled async operations
async function badExample(userId: string) {
const user = await getUserById(userId); // No error handling
return user.data; // Could fail if user is null
}
// Route definition (index.ts)
.get('/workspace-settings', async (c) => {
const user = c.get('busterUser');
const response = await getWorkspaceSettingsHandler(user);
return c.json(response);
})
```
## Test Utilities
### Authentication and User Context
- **Use requireAuth middleware** - Apply to all protected routes
- **Extract user context** - Use `c.get('busterUser')` to get the authenticated user
- **Type as User** - Import `User` type from `@buster/database` for handler parameters
The `@buster/test-utils` package provides shared testing utilities:
## Database Operations
### Environment Helpers
```typescript
import { setupTestEnvironment, withTestEnv } from '@buster/test-utils/env-helpers';
// Manual setup/teardown
beforeAll(() => setupTestEnvironment());
afterAll(() => cleanupTestEnvironment());
// Or use the wrapper
await withTestEnv(async () => {
// Your test code here
});
### Soft Delete and Upsert Practices
- In our database, we never hard delete, we always use soft deletes with the `deleted_at` field
- For update operations, we should almost always perform an upsert unless otherwise specified
```
### Mock Helpers
```typescript
import { createMockFunction, mockConsole, createMockDate } from '@buster/test-utils/mock-helpers';
// Create vitest mock functions
const mockFn = createMockFunction<(arg: string) => void>();
// Mock console methods (allowed in tests)
const consoleMock = mockConsole();
// Test code that logs...
consoleMock.restore();
// Mock dates for time-sensitive tests
const dateMock = createMockDate(new Date('2024-01-01'));
// Test code...
dateMock.restore();
```
### Database Test Helpers
```typescript
import { createTestChat, cleanupTestChats } from '@buster/test-utils/database/chats';
import { createTestMessage, cleanupTestMessages } from '@buster/test-utils/database/messages';
// Create test data
const chat = await createTestChat({
userId: 'test-user',
title: 'Test Chat'
});
const message = await createTestMessage({
chatId: chat.id,
role: 'user',
content: 'Test message'
});
// Cleanup after tests
await cleanupTestMessages(chat.id);
await cleanupTestChats('test-user');
```
## Quick Command Reference
### Building & Type Checking
```bash
# Build all packages
turbo run build
# Build specific package/app
turbo run build --filter=@buster/ai
turbo run build --filter=@buster-app/web
# Type check only
turbo run typecheck
turbo run typecheck --filter=@buster/database
```
### Linting & Formatting
```bash
# Check and auto-fix with Biome
pnpm run check:fix path/to/file.ts
pnpm run check:fix packages/ai
# Check only (no fixes)
pnpm run check path/to/file.ts
```
### Testing
```bash
# Run all tests
pnpm run test
# Run tests for specific package
turbo run test --filter=@buster/ai
# Run specific test file
pnpm run test path/to/file.test.ts
# Watch mode
pnpm run test:watch
```
### Database Commands
```bash
pnpm run db:generate # Generate types from schema
pnpm run db:migrate # Run migrations
pnpm run db:push # Push schema changes
pnpm run db:studio # Open Drizzle Studio
```
## Helper Organization Pattern
When building helper functions, follow this organizational pattern:
### Database Helpers (in `packages/database/`)
```
packages/database/src/helpers/
├── index.ts # Export all helpers
├── messages.ts # Message-related helpers
├── users.ts # User-related helpers
├── chats.ts # Chat-related helpers
└── {entity}.ts # Entity-specific helpers
```
### Package-Specific Utilities
```
packages/{package}/src/utils/
├── index.ts # Export all utilities
├── {domain}/ # Domain-specific utilities
│ ├── index.ts
│ └── helpers.ts
└── helpers.ts # General helpers
```
### Key Principles
- **Co-locate helpers** with the schema/types they operate on
- **Group by entity** (one file per database table/domain object)
- **Export from package root** for easy importing
- **Use TypeScript** with proper types (no `any`)
- **Follow naming conventions** that clearly indicate purpose
### Example Usage
```typescript
// ✅ Good: Clear, typed helpers exported from package root
import { getRawLlmMessages, getMessagesForChat } from '@buster/database';
// ❌ Avoid: Direct database queries scattered throughout codebase
import { db, messages, eq } from '@buster/database';
const result = await db.select().from(messages).where(eq(messages.chatId, chatId));
```
## Background Job Processing (Trigger.dev)
The `apps/trigger` package provides background job processing using **Trigger.dev v3**.
### 🚨 CRITICAL: Always Use v3 Patterns
```typescript
// ✅ CORRECT - Always use this pattern
import { task } from '@trigger.dev/sdk/v3';
export const myTask = task({
id: 'my-task',
run: async (payload: InputType): Promise<OutputType> => {
// Task implementation
},
});
```
### Essential Requirements
1. **MUST export every task** from the file
2. **MUST use unique task IDs** within the project
3. **MUST import from** `@trigger.dev/sdk/v3`
4. **Use Zod schemas** for payload validation
### Common Task Patterns
#### Schema-Validated Task (Recommended)
```typescript
import { schemaTask } from '@trigger.dev/sdk/v3';
import { z } from 'zod';
// Define schema for type safety
export const TaskInputSchema = z.object({
userId: z.string(),
data: z.record(z.unknown()),
});
export type TaskInput = z.infer<typeof TaskInputSchema>;
export const processUserTask = schemaTask({
id: 'process-user',
schema: TaskInputSchema,
maxDuration: 300, // 5 minutes
run: async (payload) => {
// Payload is validated and typed
return { success: true };
},
});
```
#### Triggering Tasks
```typescript
import { tasks } from '@trigger.dev/sdk/v3';
import type { processUserTask } from '@buster-app/trigger/tasks';
// Trigger from API routes
const handle = await tasks.trigger<typeof processUserTask>('process-user', {
userId: 'user123',
data: {}
});
```
### Development Commands
```bash
# Development server
pnpm run trigger:dev
# Run tests
pnpm run trigger:test
# Deploy
pnpm run trigger:deploy
```
**See `apps/trigger/CLAUDE.md` for complete Trigger.dev guidelines.**
## Key Dependencies
- **Turborepo** - Monorepo orchestration and caching
- **pnpm** - Fast, disk space efficient package manager
- **Biome** - Fast linting and formatting (replaces ESLint/Prettier)
- **TypeScript** - Strict type checking across all packages
- **Vitest** - Fast unit testing framework
- **Zod** - Runtime validation and type inference
- **Mastra** - AI agent framework for LLM workflows
- **Trigger.dev v3** - Background job processing
- **Drizzle ORM** - Type-safe database toolkit
- **Braintrust** - LLM observability and evaluation
## Complete Development Workflow Example
When implementing a new feature:
```bash
# 1. Write your modular, testable functions
# 2. Compose them into the feature
# 3. Write tests alongside the code
# 4. Ensure type safety
turbo run build --filter=@buster/ai
# or for all packages:
turbo run build
# 5. Fix linting and formatting
pnpm run check:fix packages/ai
# 6. Run tests
turbo run test --filter=@buster/ai
# or specific test:
pnpm run test packages/ai/src/feature.test.ts
# 7. If all passes, commit your changes
git add .
git commit -m "feat: add new feature"
```
## Slack Package (@buster/slack)
The `@buster/slack` package is a **standalone Slack integration** with no database dependencies. It provides:
### Features
- **OAuth 2.0 Authentication** - Complete OAuth flow with state management
- **Channel Management** - List, validate, join/leave channels
- **Messaging** - Send messages, replies, updates with retry logic
- **Message Tracking** - Interface for threading support
- **Type Safety** - Zod validation throughout
### Architecture
The package uses **interface-based design** where consuming applications must implement:
- `ISlackTokenStorage` - For token persistence
- `ISlackOAuthStateStorage` - For OAuth state management
- `ISlackMessageTracking` - For message threading (optional)
### Usage Pattern
```typescript
// All functions accept tokens as parameters
const channels = await channelService.getAvailableChannels(accessToken);
const result = await messagingService.sendMessage(accessToken, channelId, message);
```
### Testing
```bash
# Run tests
turbo run test --filter=@buster/slack
# Build
turbo run build --filter=@buster/slack
# Type check
turbo run typecheck --filter=@buster/slack
```
### Key Principles
- **No database dependencies** - Uses interfaces for storage
- **Token-based** - All functions accept tokens as parameters
- **Framework-agnostic** - Works with any Node.js application
- **Comprehensive error handling** - Typed errors with retry logic
## Important Notes
- **Never use `any`** - Biome will error on this
- **Always handle errors** properly with try-catch
- **Write tests alongside code** - not in separate folders
- **Use Zod for validation** - single source of truth
- **Run type checks** before committing
- **Follow existing patterns** in the codebase
This ensures high code quality and maintainability across the monorepo.
## Common Biome Overrides
Test files have relaxed rules to allow:
- `console.log` for debugging tests
- Non-null assertions (`!`) in test scenarios
- `any` type when mocking (though prefer proper types)
Database package allows `any` for Drizzle ORM compatibility.
## Environment Variables
The monorepo uses a strict environment mode. Key variables include:
- Database connections (Supabase, PostgreSQL, etc.)
- API keys (OpenAI, Anthropic, etc.)
- Service URLs and configurations
See `.env.example` files in each package for required variables.
**Test Running Guidelines**:
- When running tests, use the following Turbo commands:
- `turbo test:unit` for unit tests
- `turbo test:integration` for integration tests
- `turbo test` for running all tests
# important-instruction-reminders
Do what has been asked; nothing more, nothing less.
NEVER create files unless they're absolutely necessary for achieving your goal.
ALWAYS prefer editing an existing file to creating a new one.
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
## Biome Linting Instructions
### Linting Rules
- Always use `pnpm run check` or `pnpm run check:fix`
- **Rule: `I don't want Claude to ever run a biome lint fix only biome lint`**
- This means ONLY use `pnpm run check` (linting without auto-fixing)
- Do NOT use `pnpm run check:fix`
- Claude should understand to ONLY run lint checks, never auto-fix

View File

@ -2,6 +2,8 @@
This file provides guidance to Claude Code when working with code in this monorepo.
**Note**: Many packages and apps have their own CLAUDE.md files with specific implementation details and patterns. Always check for a local CLAUDE.md when working in a specific directory.
## Monorepo Structure
This is a pnpm-based monorepo using Turborepo with the following structure:
@ -28,6 +30,7 @@ This is a pnpm-based monorepo using Turborepo with the following structure:
- `packages/web-tools` - Web scraping and research tools
- `packages/slack` - Standalone Slack integration (OAuth, messaging, channels)
- `packages/supabase` - Supabase setup and configuration
- `packages/sandbox` - Sandboxed code execution using Daytona SDK
## Development Workflow
@ -37,6 +40,8 @@ When writing code, follow this workflow to ensure code quality:
- Create small, focused functions with single responsibilities
- Design functions to be easily testable with clear inputs/outputs
- Use dependency injection for external dependencies
- **IMPORTANT: Write functional, composable code - avoid classes**
- All features should be composed of testable functions
- Follow existing patterns in the codebase
### 2. Build Features by Composing Functions
@ -91,20 +96,26 @@ pnpm run test:watch
```
### 6. Pre-Completion Checklist
**IMPORTANT: Before finishing any task, always run:**
**IMPORTANT: Before finishing any task or creating PRs, always run:**
```bash
# Run unit tests for the entire monorepo
# 1. Run unit tests for the entire monorepo
turbo run test:unit
# Run linting for the entire monorepo
# 2. Build the entire monorepo to ensure everything compiles
turbo run build
# 3. Run linting for the entire monorepo
turbo run lint
```
**Key Testing Guidelines:**
- **Always run unit tests and lint** when working locally before considering a task complete
- **Always run unit tests, build, and lint** when working locally before considering a task complete
- **Unit tests** should be run for the entire monorepo to catch any breaking changes
- **Build** must pass for the entire monorepo to ensure type safety
- **Integration tests** should only be run for specific packages/features you're working on (NOT the entire monorepo)
- **Fix all failing tests and lint errors** before completing any task
- **Fix all failing tests, build errors, and lint errors** before completing any task
- **Heavily bias toward unit tests** - they are faster and cheaper to run
- **Mock everything you can** in unit tests for isolation and speed
## Code Quality Standards

View File

@ -1,6 +1,6 @@
import { chats, db, slackIntegrations } from '@buster/database';
import { chats, db, getSecretByName, slackIntegrations } from '@buster/database';
import type { SlackEventsResponse } from '@buster/server-shared/slack';
import { type SlackWebhookPayload, isEventCallback } from '@buster/slack';
import { type SlackWebhookPayload, addReaction, isEventCallback } from '@buster/slack';
import { tasks } from '@trigger.dev/sdk';
import { and, eq } from 'drizzle-orm';
import type { Context } from 'hono';
@ -188,6 +188,53 @@ export async function eventsHandler(payload: SlackWebhookPayload): Promise<Slack
// Extract thread timestamp - if no thread_ts, this is a new thread so use ts
const threadTs = event.thread_ts || event.ts;
// Add hourglass reaction immediately after authentication
if (organizationId) {
try {
// Fetch Slack integration to get token vault key
const slackIntegration = await db
.select({
tokenVaultKey: slackIntegrations.tokenVaultKey,
})
.from(slackIntegrations)
.where(
and(
eq(slackIntegrations.organizationId, organizationId),
eq(slackIntegrations.teamId, payload.team_id),
eq(slackIntegrations.status, 'active')
)
)
.limit(1);
if (slackIntegration.length > 0 && slackIntegration[0]?.tokenVaultKey) {
// Get the access token from vault
const vaultSecret = await getSecretByName(slackIntegration[0].tokenVaultKey);
if (vaultSecret?.secret) {
// Add the hourglass reaction
await addReaction({
accessToken: vaultSecret.secret,
channelId: event.channel,
messageTs: event.ts,
emoji: 'hourglass_flowing_sand',
});
console.info('Added hourglass reaction to app mention', {
channel: event.channel,
messageTs: event.ts,
});
}
}
} catch (error) {
// Log but don't fail the entire process if reaction fails
console.warn('Failed to add hourglass reaction', {
error: error instanceof Error ? error.message : 'Unknown error',
channel: event.channel,
messageTs: event.ts,
});
}
}
// Find or create chat
const chatId = await findOrCreateSlackChat({
threadTs,

View File

@ -3,7 +3,6 @@ import {
SlackMessagingService,
addReaction,
convertMarkdownToSlack,
getReactions,
getThreadMessages,
removeReaction,
} from '@buster/slack';
@ -102,9 +101,7 @@ export const slackAgentTask: ReturnType<
botUserId: integration.botUserId,
});
// Step 3: We'll add reactions after we fetch messages and find the mention
// Step 4: Fetch all needed data concurrently
// Step 3: Fetch all needed data concurrently
const [slackMessages] = await Promise.all([
getThreadMessages({
accessToken,
@ -141,59 +138,6 @@ export const slackAgentTask: ReturnType<
throw new Error('No @Buster mention found in the thread');
}
// Step 5: Add hourglass reaction to the message that mentioned @Buster
try {
// First, get existing reactions to see if we need to clean up
const existingReactions = await getReactions({
accessToken,
channelId: chatDetails.slackChannelId,
messageTs: mentionMessageTs,
});
// Remove any existing reactions from the bot
if (integration.botUserId && existingReactions.length > 0) {
const botUserId = integration.botUserId;
const botReactions = existingReactions.filter((reaction) =>
reaction.users.includes(botUserId)
);
for (const reaction of botReactions) {
try {
await removeReaction({
accessToken,
channelId: chatDetails.slackChannelId,
messageTs: mentionMessageTs,
emoji: reaction.name,
});
logger.log('Removed existing bot reaction', { emoji: reaction.name });
} catch (error) {
// Log but don't fail if we can't remove a reaction
logger.warn('Failed to remove bot reaction', {
emoji: reaction.name,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
}
}
// Add the hourglass reaction
await addReaction({
accessToken,
channelId: chatDetails.slackChannelId,
messageTs: mentionMessageTs,
emoji: 'hourglass_flowing_sand',
});
logger.log('Added hourglass reaction to message with @Buster mention', {
messageTs: mentionMessageTs,
});
} catch (error) {
// Log but don't fail the entire task if reaction handling fails
logger.warn('Failed to manage Slack reactions', {
error: error instanceof Error ? error.message : 'Unknown error',
});
}
// Find all bot messages in the thread to determine if this is a follow-up
const previousBotMessages = slackMessages.filter(
(msg) => msg.user === integration.botUserId && msg.ts < mentionMessageTs
@ -300,7 +244,7 @@ export const slackAgentTask: ReturnType<
prompt = `Please fulfill the request from this slack conversation:\n${formattedMessages}`;
}
// Step 6: Create message
// Step 4: Create message
const message = await createMessage({
chatId: payload.chatId,
userId: payload.userId,
@ -312,7 +256,7 @@ export const slackAgentTask: ReturnType<
messageId: message.id,
});
// Step 7: Trigger analyst agent task (without waiting)
// Step 5: Trigger analyst agent task (without waiting)
logger.log('Triggering analyst agent task', {
messageId: message.id,
});
@ -325,7 +269,7 @@ export const slackAgentTask: ReturnType<
runId: analystHandle.id,
});
// Step 8: Send initial Slack message immediately after triggering
// Step 6: Send initial Slack message immediately after triggering
const messagingService = new SlackMessagingService();
const busterUrl = process.env.BUSTER_URL || 'https://platform.buster.so';
let progressMessageTs: string | undefined;
@ -376,7 +320,7 @@ export const slackAgentTask: ReturnType<
});
}
// Step 9: Poll for analyst task completion
// Step 7: Poll for analyst task completion
let isComplete = false;
let analystResult: { ok: boolean; output?: unknown; error?: unknown } | null = null;
const maxPollingTime = 30 * 60 * 1000; // 30 minutes
@ -448,7 +392,7 @@ export const slackAgentTask: ReturnType<
);
}
// Step 10: Fetch the response message and chat details from the database
// Step 8: Fetch the response message and chat details from the database
let responseText = "I've finished working on your request!";
let chatFileInfo: {
mostRecentFileId: string | null;
@ -511,7 +455,7 @@ export const slackAgentTask: ReturnType<
});
}
// Step 11: Delete the initial message and send a new one with the response
// Step 9: Delete the initial message and send a new one with the response
if (progressMessageTs) {
try {
// First, delete the initial message
@ -607,7 +551,7 @@ export const slackAgentTask: ReturnType<
});
}
// Step 12: Update reactions - remove hourglass, add checkmark on the mention message
// Step 10: Update reactions - remove hourglass, add checkmark on the mention message
try {
// Remove the hourglass reaction
await removeReaction({

135
apps/web/CLAUDE.md Normal file
View File

@ -0,0 +1,135 @@
# CLAUDE.md - Web App
This file provides guidance for working with the Next.js web application.
## TypeScript & React Standards
### TypeScript Configuration
- Use TypeScript for all new code
- **Strict mode enabled** - All strict checks are on
- **No implicit any** - Always use specific types
- **Type imports** - ALWAYS use `import type` when importing only types to minimize build size
- Prefer template literals over string concatenation
### React Patterns
- Prefer functional components and hooks over class components
- Use React.memo() for performance optimization when appropriate
- Prefer async/await over .then() for asynchronous operations
- Use proper TypeScript types for all variables and functions
## Component Guidelines
### UI Components
- Use components from `@/components/ui` folder whenever possible
- For custom elements, use Tailwind CSS to define component structure
- Import `<Text>` component from `@/components/typography` for text
- Import `<Title>` component from `@/components/typography` for titles
### Routing
- When using useRouter, import from `next/navigation` (not `next/router`)
## Directory Structure
### API Directory (`src/api/`)
```
src/api/
├── asset_interfaces/ # TypeScript interfaces for assets and API responses
├── buster_rest/ # REST API client implementation
├── buster_socket/ # WebSocket client implementation
├── buster_socket_query/ # WebSocket + React Query integration
├── next/ # Next.js utilities and Axios instances
├── other/ # External API utilities (rarely used)
├── query_keys/ # TanStack Query keys by namespace
├── request_interfaces/ # TypeScript interfaces for request payloads
├── createInstance.ts # Main API instance creation
└── createServerInstance.ts # Server-side API instance
```
### API Integration Patterns
- **Type Safety**: Import response types from `asset_interfaces/` and request types from `request_interfaces/`
- **Query Keys**: Use consistent query keys from `query_keys/` for TanStack Query
- **WebSocket**: Real-time features use `buster_socket/` with proper namespacing
- **State Management**: WebSocket + React Query integration via `buster_socket_query/`
## Testing
### Test Organization
- Place test files in the same folder as the file being tested
- Name test files with `.test.ts` or `.test.tsx` extension
- Example: `text.ts``text.test.ts` in the same directory
### Testing Framework
- **Use Vitest** for all tests (NOT Jest)
- Follow the monorepo testing guidelines from the root CLAUDE.md
### Testing Commands
```bash
# Run tests for the web app
turbo run test --filter=@buster-app/web
# Run specific test file
pnpm run test path/to/file.test.ts
# Watch mode for development
pnpm run test:watch
```
## Best Practices
### Code Quality
- Follow the monorepo-wide standards from the root CLAUDE.md
- Use functional, composable code patterns
- Create small, focused functions with single responsibilities
- Mock external dependencies in unit tests
### Performance
- Use dynamic imports for code splitting
- Implement proper loading states
- Optimize images with Next.js Image component
- Use React.memo() and useMemo() where appropriate
### State Management
- Use TanStack Query for server state
- Local state with useState/useReducer
- Context API for cross-component state when needed
- Maintain consistent cache keys via `query_keys/`
## Common Patterns
### API Calls
```typescript
// Import types
import type { UserResponse } from '@/api/asset_interfaces/user';
import type { UpdateUserRequest } from '@/api/request_interfaces/user';
// Use REST client
import { busterRest } from '@/api/buster_rest';
// Make typed API call
const updateUser = async (data: UpdateUserRequest): Promise<UserResponse> => {
return busterRest.user.update(data);
};
```
### WebSocket Integration
```typescript
// Import socket client
import { busterSocket } from '@/api/buster_socket';
// Use with React Query
import { useBusterSocketQuery } from '@/api/buster_socket_query';
// Subscribe to real-time updates
const { data, isLoading } = useBusterSocketQuery({
namespace: 'metrics',
event: 'update',
queryKey: ['metrics', metricId],
});
```
## Environment Variables
- Use `NEXT_PUBLIC_` prefix for client-side variables
- Server-only variables don't need the prefix
- Type environment variables in `env.d.ts`
Remember to always check the root CLAUDE.md for monorepo-wide standards and run the pre-completion checklist before finishing any task.

View File

@ -1 +1 @@
/dist/*
/dist/

1
packages/sandbox/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/dist/