feat: add codex review failover and suppress output hardening
This commit is contained in:
@@ -870,6 +870,31 @@ describe('DiscordChannel', () => {
|
||||
expect(currentClient().channels.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not send stale typing after typing was disabled mid-flight', async () => {
|
||||
const opts = createTestOpts();
|
||||
const channel = new DiscordChannel('test-token', opts);
|
||||
await channel.connect();
|
||||
|
||||
let resolveFetch!: (value: unknown) => void;
|
||||
const fetchPromise = new Promise((resolve) => {
|
||||
resolveFetch = resolve;
|
||||
});
|
||||
const mockChannel = {
|
||||
send: vi.fn(),
|
||||
sendTyping: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
currentClient().channels.fetch.mockImplementationOnce(() => fetchPromise);
|
||||
|
||||
const typingPromise = channel.setTyping('dc:1234567890123456', true);
|
||||
await Promise.resolve();
|
||||
await channel.setTyping('dc:1234567890123456', false);
|
||||
resolveFetch(mockChannel);
|
||||
await typingPromise;
|
||||
|
||||
expect(mockChannel.sendTyping).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does nothing when client is not initialized', async () => {
|
||||
const opts = createTestOpts();
|
||||
const channel = new DiscordChannel('test-token', opts);
|
||||
|
||||
@@ -177,6 +177,7 @@ export class DiscordChannel implements Channel {
|
||||
private opts: DiscordChannelOpts;
|
||||
private botToken: string;
|
||||
private typingIntervals = new Map<string, NodeJS.Timeout>();
|
||||
private typingGenerations = new Map<string, number>();
|
||||
private agentTypeFilter?: AgentType;
|
||||
|
||||
constructor(
|
||||
@@ -539,6 +540,9 @@ export class DiscordChannel implements Channel {
|
||||
async setTyping(jid: string, isTyping: boolean): Promise<void> {
|
||||
if (!this.client) return;
|
||||
|
||||
const generation = (this.typingGenerations.get(jid) ?? 0) + 1;
|
||||
this.typingGenerations.set(jid, generation);
|
||||
|
||||
// Clear any existing interval for this channel
|
||||
const existing = this.typingIntervals.get(jid);
|
||||
if (existing) {
|
||||
@@ -548,10 +552,15 @@ export class DiscordChannel implements Channel {
|
||||
|
||||
if (!isTyping) return;
|
||||
|
||||
const isCurrentGeneration = () =>
|
||||
this.typingGenerations.get(jid) === generation;
|
||||
|
||||
const sendOnce = async () => {
|
||||
if (!isCurrentGeneration()) return;
|
||||
try {
|
||||
const channelId = jid.replace(/^dc:/, '');
|
||||
const channel = await this.client!.channels.fetch(channelId);
|
||||
if (!isCurrentGeneration()) return;
|
||||
if (channel && 'sendTyping' in channel) {
|
||||
await (channel as TextChannel).sendTyping();
|
||||
}
|
||||
@@ -562,7 +571,13 @@ export class DiscordChannel implements Channel {
|
||||
|
||||
// Send immediately, then refresh every 8 seconds (Discord expires at ~10s)
|
||||
await sendOnce();
|
||||
this.typingIntervals.set(jid, setInterval(sendOnce, 8000));
|
||||
if (!isCurrentGeneration()) return;
|
||||
this.typingIntervals.set(
|
||||
jid,
|
||||
setInterval(() => {
|
||||
void sendOnce();
|
||||
}, 8000),
|
||||
);
|
||||
}
|
||||
|
||||
async sendAndTrack(jid: string, text: string): Promise<string | null> {
|
||||
|
||||
Reference in New Issue
Block a user