159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
|
|
import * as fs from 'fs/promises';
|
|
|
|
import { TeamMemberRuntimeAdvisoryService } from '../../../../src/main/services/team/TeamMemberRuntimeAdvisoryService';
|
|
import { setClaudeBasePathOverride } from '../../../../src/main/utils/pathDecoder';
|
|
|
|
describe('TeamMemberRuntimeAdvisoryService', () => {
|
|
let tmpDir: string | null = null;
|
|
|
|
afterEach(async () => {
|
|
setClaudeBasePathOverride(null);
|
|
if (tmpDir) {
|
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
tmpDir = null;
|
|
}
|
|
});
|
|
|
|
it('returns active sdk retry advisory for a teammate log', async () => {
|
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-advisory-'));
|
|
setClaudeBasePathOverride(tmpDir);
|
|
|
|
const teamName = 'signal-ops';
|
|
const projectPath = '/Users/test/proj';
|
|
const projectId = '-Users-test-proj';
|
|
const leadSessionId = 'lead-session';
|
|
|
|
await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'teams', teamName, 'config.json'),
|
|
JSON.stringify({
|
|
name: teamName,
|
|
projectPath,
|
|
leadSessionId,
|
|
members: [
|
|
{ name: 'team-lead', agentType: 'team-lead' },
|
|
{ name: 'alice', agentType: 'general-purpose' },
|
|
],
|
|
}),
|
|
'utf8'
|
|
);
|
|
|
|
const projectRoot = path.join(tmpDir, 'projects', projectId);
|
|
await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(projectRoot, `${leadSessionId}.jsonl`),
|
|
`${JSON.stringify({
|
|
timestamp: '2026-01-01T00:00:00.000Z',
|
|
type: 'user',
|
|
message: { role: 'user', content: 'Start' },
|
|
})}\n`,
|
|
'utf8'
|
|
);
|
|
|
|
const nowIso = new Date().toISOString();
|
|
await fs.writeFile(
|
|
path.join(projectRoot, leadSessionId, 'subagents', 'agent-alice.jsonl'),
|
|
[
|
|
JSON.stringify({
|
|
timestamp: nowIso,
|
|
type: 'user',
|
|
message: { role: 'user', content: 'You are alice, a reviewer on team "signal-ops" (signal-ops).' },
|
|
}),
|
|
JSON.stringify({
|
|
timestamp: nowIso,
|
|
type: 'system',
|
|
subtype: 'api_error',
|
|
retryInMs: 45_000,
|
|
retryAttempt: 1,
|
|
maxRetries: 10,
|
|
error: {
|
|
error: {
|
|
error: {
|
|
message: 'Gemini cli backend error: capacity exceeded.',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
].join('\n') + '\n',
|
|
'utf8'
|
|
);
|
|
|
|
const service = new TeamMemberRuntimeAdvisoryService();
|
|
const advisory = await service.getMemberAdvisory(teamName, 'alice');
|
|
|
|
expect(advisory).not.toBeNull();
|
|
expect(advisory?.kind).toBe('sdk_retrying');
|
|
expect(advisory?.message).toContain('capacity exceeded');
|
|
});
|
|
|
|
it('ignores expired retry advisories', async () => {
|
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-team-advisory-'));
|
|
setClaudeBasePathOverride(tmpDir);
|
|
|
|
const teamName = 'signal-ops';
|
|
const projectPath = '/Users/test/proj';
|
|
const projectId = '-Users-test-proj';
|
|
const leadSessionId = 'lead-session';
|
|
|
|
await fs.mkdir(path.join(tmpDir, 'teams', teamName), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'teams', teamName, 'config.json'),
|
|
JSON.stringify({
|
|
name: teamName,
|
|
projectPath,
|
|
leadSessionId,
|
|
members: [
|
|
{ name: 'team-lead', agentType: 'team-lead' },
|
|
{ name: 'alice', agentType: 'general-purpose' },
|
|
],
|
|
}),
|
|
'utf8'
|
|
);
|
|
|
|
const projectRoot = path.join(tmpDir, 'projects', projectId);
|
|
await fs.mkdir(path.join(projectRoot, leadSessionId, 'subagents'), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(projectRoot, `${leadSessionId}.jsonl`),
|
|
`${JSON.stringify({
|
|
timestamp: '2026-01-01T00:00:00.000Z',
|
|
type: 'user',
|
|
message: { role: 'user', content: 'Start' },
|
|
})}\n`,
|
|
'utf8'
|
|
);
|
|
|
|
await fs.writeFile(
|
|
path.join(projectRoot, leadSessionId, 'subagents', 'agent-alice.jsonl'),
|
|
[
|
|
JSON.stringify({
|
|
timestamp: new Date(Date.now() - 60_000).toISOString(),
|
|
type: 'user',
|
|
message: { role: 'user', content: 'You are alice, a reviewer on team "signal-ops" (signal-ops).' },
|
|
}),
|
|
JSON.stringify({
|
|
timestamp: new Date(Date.now() - 60_000).toISOString(),
|
|
type: 'system',
|
|
subtype: 'api_error',
|
|
retryInMs: 5_000,
|
|
retryAttempt: 1,
|
|
maxRetries: 10,
|
|
error: {
|
|
error: {
|
|
error: {
|
|
message: 'Old retry window',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
].join('\n') + '\n',
|
|
'utf8'
|
|
);
|
|
|
|
const service = new TeamMemberRuntimeAdvisoryService();
|
|
await expect(service.getMemberAdvisory(teamName, 'alice')).resolves.toBeNull();
|
|
});
|
|
});
|