This commit is contained in:
170
packages/server/tests/daemons/hive-mind.test.ts
Normal file
170
packages/server/tests/daemons/hive-mind.test.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { buildServer } from '../../src/index.js';
|
||||
import { users, teams, teamMembers, teamResources, agentJobs, tasks, messages } from '../../src/db/schema.js';
|
||||
import { sql, eq } from 'drizzle-orm';
|
||||
import { HiveMindAgent } from '../../src/daemons/hive-mind.js';
|
||||
|
||||
describe('Hive Mind Agent (Task 3.20)', () => {
|
||||
let server: Awaited<ReturnType<typeof buildServer>>;
|
||||
let ownerId: string;
|
||||
let memberId: string;
|
||||
let teamId: string;
|
||||
let hiveMind: HiveMindAgent;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = await buildServer();
|
||||
|
||||
// Clean up leftover test data
|
||||
await server.db.execute(sql`DELETE FROM messages WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM agent_jobs WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM tasks WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_resources WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'hivetest-%'`);
|
||||
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'hive_%'`);
|
||||
|
||||
// Create test users
|
||||
const [owner] = await server.db.insert(users).values({
|
||||
clerkId: 'hive_owner',
|
||||
displayName: 'Hive Owner',
|
||||
email: 'hive_owner@test.com',
|
||||
}).returning();
|
||||
ownerId = owner.id;
|
||||
|
||||
const [member] = await server.db.insert(users).values({
|
||||
clerkId: 'hive_member',
|
||||
displayName: 'Hive Member',
|
||||
email: 'hive_member@test.com',
|
||||
}).returning();
|
||||
memberId = member.id;
|
||||
|
||||
// Create team
|
||||
const [team] = await server.db.insert(teams).values({
|
||||
name: 'Hive Test Team',
|
||||
slug: 'hivetest-team',
|
||||
ownerId,
|
||||
}).returning();
|
||||
teamId = team.id;
|
||||
|
||||
await server.db.insert(teamMembers).values([
|
||||
{ teamId, userId: ownerId, role: 'owner' },
|
||||
{ teamId, userId: memberId, role: 'member' },
|
||||
]);
|
||||
|
||||
// Seed data: completed jobs
|
||||
await server.db.insert(agentJobs).values(
|
||||
Array.from({ length: 5 }, (_, i) => ({
|
||||
teamId,
|
||||
userId: ownerId,
|
||||
jobType: 'chat',
|
||||
status: 'completed',
|
||||
input: { prompt: `job ${i}` },
|
||||
completedAt: new Date(),
|
||||
})),
|
||||
);
|
||||
|
||||
// Seed data: tasks with similar titles by different users (duplicate work)
|
||||
await server.db.insert(tasks).values([
|
||||
{ teamId, title: 'Setup CI/CD pipeline for backend', status: 'done', createdBy: ownerId },
|
||||
{ teamId, title: 'Setup CI/CD pipeline for frontend', status: 'done', createdBy: memberId },
|
||||
{ teamId, title: 'Write unit tests', status: 'done', createdBy: ownerId },
|
||||
]);
|
||||
|
||||
// Seed data: team resources with ratings
|
||||
await server.db.insert(teamResources).values([
|
||||
{
|
||||
teamId,
|
||||
resourceType: 'prompt_template',
|
||||
name: 'Best Code Review Prompt',
|
||||
description: 'A highly rated code review template',
|
||||
config: {},
|
||||
sharedBy: ownerId,
|
||||
rating: 4.5,
|
||||
},
|
||||
{
|
||||
teamId,
|
||||
resourceType: 'skill',
|
||||
name: 'Low Rated Skill',
|
||||
description: 'Not very useful',
|
||||
config: {},
|
||||
sharedBy: memberId,
|
||||
rating: 1.0,
|
||||
},
|
||||
]);
|
||||
|
||||
hiveMind = new HiveMindAgent(server.db);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.db.execute(sql`DELETE FROM messages WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM agent_jobs WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM tasks WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_resources WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'hivetest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'hivetest-%'`);
|
||||
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'hive_%'`);
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('generates weekly digest with metrics', async () => {
|
||||
const { digest, messageId } = await hiveMind.generateWeeklyDigest(teamId);
|
||||
|
||||
expect(digest).toBeTruthy();
|
||||
expect(digest.metrics.jobsCompleted).toBe(5);
|
||||
expect(digest.metrics.tasksCompleted).toBe(3);
|
||||
expect(digest.metrics.resourcesShared).toBe(2);
|
||||
expect(digest.period.from).toBeInstanceOf(Date);
|
||||
expect(digest.period.to).toBeInstanceOf(Date);
|
||||
expect(messageId).toBeTruthy();
|
||||
});
|
||||
|
||||
it('detects duplicate work across team members', async () => {
|
||||
const { digest } = await hiveMind.generateWeeklyDigest(teamId);
|
||||
|
||||
// "Setup CI/CD pipeline" tasks by different users should be detected
|
||||
expect(digest.duplicateWork.length).toBeGreaterThan(0);
|
||||
const duplicate = digest.duplicateWork[0];
|
||||
expect(duplicate.users.length).toBe(2);
|
||||
expect(duplicate.titles.length).toBe(2);
|
||||
});
|
||||
|
||||
it('identifies best practices from high-rated resources', async () => {
|
||||
const { digest } = await hiveMind.generateWeeklyDigest(teamId);
|
||||
|
||||
// Only the high-rated resource (4.5 >= 3.0) should appear
|
||||
expect(digest.bestPractices.length).toBe(1);
|
||||
expect(digest.bestPractices[0].name).toBe('Best Code Review Prompt');
|
||||
expect(digest.bestPractices[0].rating).toBe(4.5);
|
||||
});
|
||||
|
||||
it('generates recommendations based on findings', async () => {
|
||||
const { digest } = await hiveMind.generateWeeklyDigest(teamId);
|
||||
|
||||
// Should have duplicate work recommendation
|
||||
expect(digest.recommendations.length).toBeGreaterThan(0);
|
||||
expect(digest.recommendations.some(r => r.includes('duplicate'))).toBe(true);
|
||||
// Should have best practices recommendation
|
||||
expect(digest.recommendations.some(r => r.includes('highly-rated'))).toBe(true);
|
||||
});
|
||||
|
||||
it('broadcasts digest as a waggle dance message', async () => {
|
||||
const { messageId } = await hiveMind.generateWeeklyDigest(teamId);
|
||||
|
||||
// Verify the message was stored
|
||||
const [msg] = await server.db.select().from(messages)
|
||||
.where(eq(messages.id, messageId));
|
||||
|
||||
expect(msg).toBeTruthy();
|
||||
expect(msg.type).toBe('broadcast');
|
||||
expect(msg.subtype).toBe('discovery');
|
||||
const digestContent = msg.content as { type: string; digest: unknown };
|
||||
expect(digestContent.type).toBe('weekly_digest');
|
||||
expect(digestContent.digest).toBeTruthy();
|
||||
});
|
||||
});
|
||||
184
packages/server/tests/daemons/scout.test.ts
Normal file
184
packages/server/tests/daemons/scout.test.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { buildServer } from '../../src/index.js';
|
||||
import { users, teams, teamMembers, teamResources, agents, scoutFindings } from '../../src/db/schema.js';
|
||||
import { sql, eq } from 'drizzle-orm';
|
||||
import { ScoutAgent } from '../../src/daemons/scout.js';
|
||||
|
||||
describe('Scout Agent (Task 3.18)', () => {
|
||||
let server: Awaited<ReturnType<typeof buildServer>>;
|
||||
let userId: string;
|
||||
let teamId: string;
|
||||
let scout: ScoutAgent;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = await buildServer();
|
||||
|
||||
// Clean up leftover test data
|
||||
await server.db.execute(sql`DELETE FROM scout_findings WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'scout_%')`);
|
||||
await server.db.execute(sql`DELETE FROM agents WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'scout_%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_resources WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'scouttest-%'`);
|
||||
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'scout_%'`);
|
||||
|
||||
// Create test user
|
||||
const [user] = await server.db.insert(users).values({
|
||||
clerkId: 'scout_user1',
|
||||
displayName: 'Scout User',
|
||||
email: 'scout_user1@test.com',
|
||||
}).returning();
|
||||
userId = user.id;
|
||||
|
||||
// Create team
|
||||
const [team] = await server.db.insert(teams).values({
|
||||
name: 'Scout Test Team',
|
||||
slug: 'scouttest-team',
|
||||
ownerId: userId,
|
||||
}).returning();
|
||||
teamId = team.id;
|
||||
|
||||
// Add membership with interests
|
||||
await server.db.insert(teamMembers).values({
|
||||
teamId,
|
||||
userId,
|
||||
role: 'owner',
|
||||
interests: ['python', 'data-science'],
|
||||
});
|
||||
|
||||
// Create an agent with tools for the user
|
||||
await server.db.insert(agents).values({
|
||||
userId,
|
||||
teamId,
|
||||
name: 'TestAgent',
|
||||
tools: ['web_search', 'code_review'],
|
||||
});
|
||||
|
||||
// Create team resources
|
||||
await server.db.insert(teamResources).values([
|
||||
{
|
||||
teamId,
|
||||
resourceType: 'skill',
|
||||
name: 'Python Data Analyzer',
|
||||
description: 'A skill for analyzing data with Python',
|
||||
config: {},
|
||||
sharedBy: userId,
|
||||
},
|
||||
{
|
||||
teamId,
|
||||
resourceType: 'prompt_template',
|
||||
name: 'Code Review Template',
|
||||
description: 'Template for code reviews',
|
||||
config: {},
|
||||
sharedBy: userId,
|
||||
},
|
||||
]);
|
||||
|
||||
scout = new ScoutAgent(server.db);
|
||||
|
||||
// Override auth handler
|
||||
server._authHandler.fn = async function (request: FastifyRequest, reply: FastifyReply) {
|
||||
const testUserId = request.headers['x-test-user-id'] as string;
|
||||
if (!testUserId) {
|
||||
return reply.code(401).send({ error: 'Missing x-test-user-id header' });
|
||||
}
|
||||
request.userId = testUserId;
|
||||
request.clerkId = 'test';
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.db.execute(sql`DELETE FROM scout_findings WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'scout_%')`);
|
||||
await server.db.execute(sql`DELETE FROM agents WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'scout_%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_resources WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'scouttest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'scouttest-%'`);
|
||||
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'scout_%'`);
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('scan creates findings from team resources', async () => {
|
||||
const findings = await scout.scan(userId, teamId);
|
||||
expect(findings.length).toBeGreaterThanOrEqual(2);
|
||||
expect(findings.every((f) => f.status === 'new')).toBe(true);
|
||||
expect(findings.some((f) => f.title.includes('Python Data Analyzer'))).toBe(true);
|
||||
expect(findings.some((f) => f.title.includes('Code Review Template'))).toBe(true);
|
||||
});
|
||||
|
||||
it('relevance scoring boosts based on user interests', async () => {
|
||||
// The Python Data Analyzer should be boosted because user has 'python' interest
|
||||
const findings = await scout.listFindings(userId);
|
||||
const pythonFinding = findings.find((f) => f.title.includes('Python'));
|
||||
const templateFinding = findings.find((f) => f.title.includes('Code Review'));
|
||||
|
||||
expect(pythonFinding).toBeTruthy();
|
||||
expect(templateFinding).toBeTruthy();
|
||||
// Python finding should have higher score (base 0.5 + 0.3 interest boost)
|
||||
expect(pythonFinding!.relevanceScore).toBeGreaterThan(templateFinding!.relevanceScore);
|
||||
});
|
||||
|
||||
it('adopt updates status', async () => {
|
||||
const findings = await scout.listFindings(userId);
|
||||
const finding = findings[0];
|
||||
|
||||
const updated = await scout.adopt(finding.id);
|
||||
expect(updated).toBeTruthy();
|
||||
expect(updated!.status).toBe('adopted');
|
||||
});
|
||||
|
||||
it('dismiss updates status', async () => {
|
||||
const findings = await scout.listFindings(userId);
|
||||
const newFinding = findings.find((f) => f.status === 'new');
|
||||
expect(newFinding).toBeTruthy();
|
||||
|
||||
const updated = await scout.dismiss(newFinding!.id);
|
||||
expect(updated).toBeTruthy();
|
||||
expect(updated!.status).toBe('dismissed');
|
||||
});
|
||||
|
||||
it('dismissed finding not resurfaced in future scans', async () => {
|
||||
// Clear all non-dismissed findings
|
||||
const allFindings = await scout.listFindings(userId);
|
||||
const dismissed = allFindings.filter((f) => f.status === 'dismissed');
|
||||
expect(dismissed.length).toBeGreaterThan(0);
|
||||
|
||||
// Run scan again — dismissed titles should not reappear as new
|
||||
const newFindings = await scout.scan(userId, teamId);
|
||||
const dismissedTitles = dismissed.map((f) => f.title);
|
||||
const resurfaced = newFindings.filter((f) => dismissedTitles.includes(f.title));
|
||||
expect(resurfaced.length).toBe(0);
|
||||
});
|
||||
|
||||
it('GET /api/scout/findings returns findings for user', async () => {
|
||||
const response = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/scout/findings',
|
||||
headers: { 'x-test-user-id': userId },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const body = JSON.parse(response.body);
|
||||
expect(Array.isArray(body)).toBe(true);
|
||||
expect(body.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('PATCH /api/scout/findings/:id rejects invalid status', async () => {
|
||||
const findings = await scout.listFindings(userId);
|
||||
const finding = findings[0];
|
||||
|
||||
const response = await server.inject({
|
||||
method: 'PATCH',
|
||||
url: `/api/scout/findings/${finding.id}`,
|
||||
headers: { 'x-test-user-id': userId },
|
||||
payload: { status: 'invalid' },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
130
packages/server/tests/daemons/subconscious.test.ts
Normal file
130
packages/server/tests/daemons/subconscious.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { buildServer } from '../../src/index.js';
|
||||
import { users, teams, teamMembers, agentJobs, agentAuditLog } from '../../src/db/schema.js';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { SubconsciousAgent } from '../../src/daemons/subconscious.js';
|
||||
|
||||
describe('Subconscious Agent (Task 3.19)', () => {
|
||||
let server: Awaited<ReturnType<typeof buildServer>>;
|
||||
let userId: string;
|
||||
let teamId: string;
|
||||
let subconscious: SubconsciousAgent;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = await buildServer();
|
||||
|
||||
// Clean up leftover test data
|
||||
await server.db.execute(sql`DELETE FROM agent_audit_log WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'subcon_%')`);
|
||||
await server.db.execute(sql`DELETE FROM agent_jobs WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'subcon_%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'subcontest-%'`);
|
||||
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'subcon_%'`);
|
||||
|
||||
// Create test user
|
||||
const [user] = await server.db.insert(users).values({
|
||||
clerkId: 'subcon_user1',
|
||||
displayName: 'Subconscious User',
|
||||
email: 'subcon_user1@test.com',
|
||||
}).returning();
|
||||
userId = user.id;
|
||||
|
||||
// Create team
|
||||
const [team] = await server.db.insert(teams).values({
|
||||
name: 'Subconscious Test Team',
|
||||
slug: 'subcontest-team',
|
||||
ownerId: userId,
|
||||
}).returning();
|
||||
teamId = team.id;
|
||||
|
||||
await server.db.insert(teamMembers).values({
|
||||
teamId,
|
||||
userId,
|
||||
role: 'owner',
|
||||
});
|
||||
|
||||
subconscious = new SubconsciousAgent(server.db);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.db.execute(sql`DELETE FROM agent_audit_log WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'subcon_%')`);
|
||||
await server.db.execute(sql`DELETE FROM agent_jobs WHERE user_id IN (SELECT id FROM users WHERE clerk_id LIKE 'subcon_%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_members WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_requests WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_overrides WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM team_capability_policies WHERE team_id IN (SELECT id FROM teams WHERE slug LIKE 'subcontest-%')`);
|
||||
await server.db.execute(sql`DELETE FROM teams WHERE slug LIKE 'subcontest-%'`);
|
||||
await server.db.execute(sql`DELETE FROM users WHERE clerk_id LIKE 'subcon_%'`);
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it('shouldReflect returns false when under threshold', async () => {
|
||||
// No completed jobs yet
|
||||
const result = await subconscious.shouldReflect(userId);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('shouldReflect returns true after N completed jobs', async () => {
|
||||
// Insert 10 completed jobs (SUBCONSCIOUS_INTERACTION_THRESHOLD = 10)
|
||||
const jobValues = Array.from({ length: 10 }, (_, i) => ({
|
||||
teamId,
|
||||
userId,
|
||||
jobType: 'chat',
|
||||
status: 'completed',
|
||||
input: { prompt: `test ${i}` },
|
||||
completedAt: new Date(),
|
||||
}));
|
||||
await server.db.insert(agentJobs).values(jobValues);
|
||||
|
||||
const result = await subconscious.shouldReflect(userId);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('reflect creates audit entry', async () => {
|
||||
const { auditEntry, insights } = await subconscious.reflect(userId);
|
||||
|
||||
expect(auditEntry).toBeTruthy();
|
||||
expect(auditEntry.agentName).toBe('subconscious');
|
||||
expect(auditEntry.actionType).toBe('subconscious_reflection');
|
||||
expect(auditEntry.description).toContain('Reflected on');
|
||||
expect(Array.isArray(insights)).toBe(true);
|
||||
});
|
||||
|
||||
it('detects repeated job types pattern', async () => {
|
||||
// Insert 5 more jobs of same type to trigger pattern detection
|
||||
const repeatedJobs = Array.from({ length: 5 }, (_, i) => ({
|
||||
teamId,
|
||||
userId,
|
||||
jobType: 'task',
|
||||
status: 'completed',
|
||||
input: { prompt: `repeated ${i}` },
|
||||
completedAt: new Date(),
|
||||
}));
|
||||
await server.db.insert(agentJobs).values(repeatedJobs);
|
||||
|
||||
const { insights } = await subconscious.reflect(userId);
|
||||
|
||||
// Should find a prompt_change insight for the repeated 'chat' type (10 jobs)
|
||||
const promptInsight = insights.find(i => i.type === 'prompt_change');
|
||||
expect(promptInsight).toBeTruthy();
|
||||
expect(promptInsight!.description).toContain('executed');
|
||||
expect(promptInsight!.description).toContain('times recently');
|
||||
});
|
||||
|
||||
it('prompt change insight requires approval in audit entry', async () => {
|
||||
// The previous reflect should have created an audit entry with requiresApproval
|
||||
const entries = await server.db.select().from(agentAuditLog)
|
||||
.where(sql`${agentAuditLog.userId} = ${userId} AND ${agentAuditLog.actionType} = 'subconscious_reflection'`);
|
||||
|
||||
// Find one that has insights with prompt_change
|
||||
const withApproval = entries.find(e => {
|
||||
const state = e.afterState as { insights?: Array<{ type: string }> } | null;
|
||||
return state?.insights?.some((i) => i.type === 'prompt_change');
|
||||
});
|
||||
|
||||
expect(withApproval).toBeTruthy();
|
||||
expect(withApproval!.requiresApproval).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user