Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/backend/src/ai/ai.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,12 @@ Do NOT include:

Requirements:
- Traits must be concise, concrete, and attributable to the user.
- Write each trait in third person using their Slack ID placeholder if provided context supports it.
- No duplicates or near-duplicates.
- Prefer quality over quantity. If only 4 strong traits exist, return 4.

Output format:
- Return ONLY a JSON array of strings.
- Example: ["JR-15 prefers TypeScript as his primary programming language", "JR-15 strongly dislikes Donald Trump"]
- Example: ["Prefers TypeScript as his primary programming language", "Strongly dislikes Donald Trump"]
Comment thread
sfreeman422 marked this conversation as resolved.
- If no strong traits are present, return []`;
Comment thread
sfreeman422 marked this conversation as resolved.

export const DAILY_MEMORY_JOB_CONCURRENCY = 50;
53 changes: 0 additions & 53 deletions packages/backend/src/ai/ai.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ const { generateText, generateImage, promptWithHistory, sendEphemeral, setCustom
clearCustomPrompt: vi.fn().mockResolvedValue(true),
}));

const { getAllTraitsForUser } = vi.hoisted(() => ({
getAllTraitsForUser: vi.fn().mockResolvedValue([]),
}));

vi.mock('./ai.service', async () => ({
AIService: classMock(() => ({
generateText,
Expand All @@ -32,12 +28,6 @@ vi.mock('../shared/services/web/web.service', async () => ({
})),
}));

vi.mock('./trait/trait.persistence.service', async () => ({
TraitPersistenceService: classMock(() => ({
getAllTraitsForUser,
})),
}));

vi.mock('../shared/middleware/suppression', async () => ({
suppressedMiddleware: (_req: unknown, _res: unknown, next: () => void) => next(),
}));
Expand All @@ -61,7 +51,6 @@ describe('aiController', () => {
vi.clearAllMocks();
setCustomPrompt.mockResolvedValue(true);
clearCustomPrompt.mockResolvedValue(true);
getAllTraitsForUser.mockResolvedValue([]);
});

it('handles /text', async () => {
Expand Down Expand Up @@ -98,48 +87,6 @@ describe('aiController', () => {
expect(sendEphemeral).toHaveBeenCalled();
});

describe('/traits', () => {
it('returns immediate 200 and sends formatted traits ephemerally', async () => {
getAllTraitsForUser.mockResolvedValue([
{
content: 'JR-15 prefers TypeScript as his programming language',
updatedAt: new Date('2026-04-01T00:00:00.000Z'),
},
]);

await request(app).post('/traits').send({ user_id: 'U1', team_id: 'T1', channel_id: 'C1' }).expect(200);

await Promise.resolve();

expect(getAllTraitsForUser).toHaveBeenCalledWith('U1', 'T1');
expect(sendEphemeral).toHaveBeenCalledWith(
'C1',
expect.stringContaining("Moonbeam's core traits about you:"),
'U1',
);
});

it('sends no-traits message when user has no traits', async () => {
getAllTraitsForUser.mockResolvedValue([]);

await request(app).post('/traits').send({ user_id: 'U1', team_id: 'T1', channel_id: 'C1' }).expect(200);

await Promise.resolve();

expect(sendEphemeral).toHaveBeenCalledWith('C1', "Moonbeam doesn't have any core traits about you yet.", 'U1');
});

it('sends fallback error message when trait retrieval fails', async () => {
getAllTraitsForUser.mockRejectedValueOnce(new Error('db fail'));

await request(app).post('/traits').send({ user_id: 'U1', team_id: 'T1', channel_id: 'C1' }).expect(200);

await Promise.resolve();

expect(sendEphemeral).toHaveBeenCalledWith('C1', 'Sorry, something went wrong fetching your traits.', 'U1');
});
});

describe('/set-prompt', () => {
it('clears prompt when text is "clear"', async () => {
const res = await request(app)
Expand Down
40 changes: 0 additions & 40 deletions packages/backend/src/ai/ai.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,15 @@ import { aiMiddleware } from './middleware/aiMiddleware';
import type { SlashCommandRequest } from '../shared/models/slack/slack-models';
import { logError } from '../shared/logger/error-logging';
import { logger } from '../shared/logger/logger';
import { TraitPersistenceService } from './trait/trait.persistence.service';

export const aiController: Router = express.Router();

const webService = new WebService();
const aiService = new AIService();
const traitPersistenceService = new TraitPersistenceService();
const aiLogger = logger.child({ module: 'AIController' });

aiController.use(suppressedMiddleware);

aiController.post('/traits', (req, res) => {
const { user_id, team_id, channel_id } = req.body;

// Respond immediately — Slack requires a response within 3 seconds
res.status(200).send('');

void (async () => {
try {
const traits = await traitPersistenceService.getAllTraitsForUser(user_id, team_id);

if (traits.length === 0) {
void webService.sendEphemeral(channel_id, "Moonbeam doesn't have any core traits about you yet.", user_id);
return;
}

const formattedTraits = traits
.map((trait, index) => {
const date = new Date(trait.updatedAt).toLocaleDateString('en-US', {
month: 'short',
year: 'numeric',
});
return `${index + 1}. "${trait.content}" (${date.toLowerCase()})`;
})
.join('\n');

const message = `Moonbeam's core traits about you:\n${formattedTraits}`;
void webService.sendEphemeral(channel_id, message, user_id);
} catch (e) {
logError(aiLogger, 'Failed to fetch traits for /ai/traits command', e, {
userId: user_id,
teamId: team_id,
channelId: channel_id,
});
void webService.sendEphemeral(channel_id, 'Sorry, something went wrong fetching your traits.', user_id);
}
})();
});

aiController.use(textMiddleware);
aiController.use(aiMiddleware);

Expand Down
Loading
Loading