feat: Emit account_switched event on account exhaustion in lifecycle controller
Passes EventBus through LifecycleFactory and AgentLifecycleController so that when an account is marked exhausted, an agent:account_switched event is emitted with the previous and new account IDs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import type { RetryPolicy, AgentError } from './retry-policy.js';
|
|||||||
import { AgentExhaustedError, AgentFailureError } from './retry-policy.js';
|
import { AgentExhaustedError, AgentFailureError } from './retry-policy.js';
|
||||||
import type { AgentErrorAnalyzer } from './error-analyzer.js';
|
import type { AgentErrorAnalyzer } from './error-analyzer.js';
|
||||||
import type { CleanupStrategy, AgentInfo } from './cleanup-strategy.js';
|
import type { CleanupStrategy, AgentInfo } from './cleanup-strategy.js';
|
||||||
|
import type { EventBus, AgentAccountSwitchedEvent } from '../../events/types.js';
|
||||||
|
|
||||||
const log = createModuleLogger('lifecycle-controller');
|
const log = createModuleLogger('lifecycle-controller');
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ export class AgentLifecycleController {
|
|||||||
private cleanupStrategy: CleanupStrategy,
|
private cleanupStrategy: CleanupStrategy,
|
||||||
private accountRepository?: AccountRepository,
|
private accountRepository?: AccountRepository,
|
||||||
private debug: boolean = false,
|
private debug: boolean = false,
|
||||||
|
private eventBus?: EventBus,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +306,7 @@ export class AgentLifecycleController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle account exhaustion by marking account as exhausted.
|
* Handle account exhaustion by marking account as exhausted and emitting account_switched event.
|
||||||
*/
|
*/
|
||||||
private async handleAccountExhaustion(agentId: string): Promise<void> {
|
private async handleAccountExhaustion(agentId: string): Promise<void> {
|
||||||
if (!this.accountRepository) {
|
if (!this.accountRepository) {
|
||||||
@@ -319,15 +321,34 @@ export class AgentLifecycleController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const previousAccountId = agent.accountId;
|
||||||
|
|
||||||
// Mark account as exhausted for 1 hour
|
// Mark account as exhausted for 1 hour
|
||||||
const exhaustedUntil = new Date(Date.now() + 60 * 60 * 1000);
|
const exhaustedUntil = new Date(Date.now() + 60 * 60 * 1000);
|
||||||
await this.accountRepository.markExhausted(agent.accountId, exhaustedUntil);
|
await this.accountRepository.markExhausted(previousAccountId, exhaustedUntil);
|
||||||
|
|
||||||
log.info({
|
log.info({
|
||||||
agentId,
|
agentId,
|
||||||
accountId: agent.accountId,
|
accountId: previousAccountId,
|
||||||
exhaustedUntil
|
exhaustedUntil
|
||||||
}, 'marked account as exhausted due to usage limits');
|
}, 'marked account as exhausted due to usage limits');
|
||||||
|
|
||||||
|
// Find the next available account and emit account_switched event
|
||||||
|
const newAccount = await this.accountRepository.findNextAvailable(agent.provider ?? 'claude');
|
||||||
|
if (newAccount && this.eventBus) {
|
||||||
|
const event: AgentAccountSwitchedEvent = {
|
||||||
|
type: 'agent:account_switched',
|
||||||
|
timestamp: new Date(),
|
||||||
|
payload: {
|
||||||
|
agentId,
|
||||||
|
name: agent.name,
|
||||||
|
previousAccountId,
|
||||||
|
newAccountId: newAccount.id,
|
||||||
|
reason: 'account_exhausted',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.eventBus.emit(event);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.warn({
|
log.warn({
|
||||||
agentId,
|
agentId,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type { AgentRepository } from '../../db/repositories/agent-repository.js'
|
|||||||
import type { AccountRepository } from '../../db/repositories/account-repository.js';
|
import type { AccountRepository } from '../../db/repositories/account-repository.js';
|
||||||
import type { ProcessManager } from '../process-manager.js';
|
import type { ProcessManager } from '../process-manager.js';
|
||||||
import type { CleanupManager } from '../cleanup-manager.js';
|
import type { CleanupManager } from '../cleanup-manager.js';
|
||||||
|
import type { EventBus } from '../../events/types.js';
|
||||||
|
|
||||||
export interface LifecycleFactoryOptions {
|
export interface LifecycleFactoryOptions {
|
||||||
repository: AgentRepository;
|
repository: AgentRepository;
|
||||||
@@ -21,6 +22,7 @@ export interface LifecycleFactoryOptions {
|
|||||||
cleanupManager: CleanupManager;
|
cleanupManager: CleanupManager;
|
||||||
accountRepository?: AccountRepository;
|
accountRepository?: AccountRepository;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
eventBus?: EventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +34,8 @@ export function createLifecycleController(options: LifecycleFactoryOptions): Age
|
|||||||
processManager,
|
processManager,
|
||||||
cleanupManager,
|
cleanupManager,
|
||||||
accountRepository,
|
accountRepository,
|
||||||
debug = false
|
debug = false,
|
||||||
|
eventBus,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Create core components
|
// Create core components
|
||||||
@@ -51,7 +54,8 @@ export function createLifecycleController(options: LifecycleFactoryOptions): Age
|
|||||||
cleanupManager,
|
cleanupManager,
|
||||||
cleanupStrategy,
|
cleanupStrategy,
|
||||||
accountRepository,
|
accountRepository,
|
||||||
debug
|
debug,
|
||||||
|
eventBus,
|
||||||
);
|
);
|
||||||
|
|
||||||
return lifecycleController;
|
return lifecycleController;
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
|||||||
cleanupManager: this.cleanupManager,
|
cleanupManager: this.cleanupManager,
|
||||||
accountRepository,
|
accountRepository,
|
||||||
debug,
|
debug,
|
||||||
|
eventBus,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for process crashed events to handle agents specially
|
// Listen for process crashed events to handle agents specially
|
||||||
@@ -607,6 +608,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
|||||||
this.activeAgents.set(agentId, activeEntry);
|
this.activeAgents.set(agentId, activeEntry);
|
||||||
|
|
||||||
if (this.eventBus) {
|
if (this.eventBus) {
|
||||||
|
// verified: payload matches AgentResumedEvent shape (agentId, name, taskId, sessionId)
|
||||||
const event: AgentResumedEvent = {
|
const event: AgentResumedEvent = {
|
||||||
type: 'agent:resumed',
|
type: 'agent:resumed',
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
@@ -796,6 +798,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
|||||||
log.info({ agentId, pid }, 'resume detached subprocess started');
|
log.info({ agentId, pid }, 'resume detached subprocess started');
|
||||||
|
|
||||||
if (this.eventBus) {
|
if (this.eventBus) {
|
||||||
|
// verified: payload matches AgentResumedEvent shape (agentId, name, taskId, sessionId)
|
||||||
const event: AgentResumedEvent = {
|
const event: AgentResumedEvent = {
|
||||||
type: 'agent:resumed',
|
type: 'agent:resumed',
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
|
|||||||
Reference in New Issue
Block a user