refactor: DB-driven agent output events with single emission point
DB log chunk insertion is now the sole trigger for agent:output events. Eliminates triple emission (FileTailer, handleStreamEvent, output buffer) in favor of: FileTailer.onRawContent → DB insert → EventBus emit. - createLogChunkCallback emits agent:output after successful DB insert - spawnInternal now wires onRawContent callback (fixes session 1 gap) - Remove eventBus from FileTailer (no longer touches EventBus) - Remove eventBus from ProcessManager constructor (dead parameter) - Remove agent:output emission from handleStreamEvent text_delta - Remove outputBuffers map and all buffer helpers from manager/handler - Remove getOutputBuffer from AgentManager interface and implementations - getAgentOutput tRPC: DB-only, no file fallback - onAgentOutput subscription: no initial buffer yield, events only - AgentOutputViewer: accumulates raw JSONL chunks, parses uniformly
This commit is contained in:
@@ -59,7 +59,6 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
private static readonly MAX_COMMIT_RETRIES = 1;
|
||||
|
||||
private activeAgents: Map<string, ActiveAgent> = new Map();
|
||||
private outputBuffers: Map<string, string[]> = new Map();
|
||||
private commitRetryCount: Map<string, number> = new Map();
|
||||
private processManager: ProcessManager;
|
||||
private credentialHandler: CredentialHandler;
|
||||
@@ -83,7 +82,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
private debug: boolean = false,
|
||||
) {
|
||||
this.signalManager = new FileSystemSignalManager();
|
||||
this.processManager = new ProcessManager(workspaceRoot, projectRepository, eventBus);
|
||||
this.processManager = new ProcessManager(workspaceRoot, projectRepository);
|
||||
this.credentialHandler = new CredentialHandler(workspaceRoot, accountRepository, credentialManager);
|
||||
this.outputHandler = new OutputHandler(repository, eventBus, changeSetRepository, phaseRepository, taskRepository, pageRepository, this.signalManager);
|
||||
this.cleanupManager = new CleanupManager(workspaceRoot, repository, projectRepository, eventBus, debug, this.signalManager);
|
||||
@@ -105,13 +104,12 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
|
||||
/**
|
||||
* Centralized cleanup of all in-memory state for an agent.
|
||||
* Cancels polling timer, removes from activeAgents, outputBuffers, and commitRetryCount.
|
||||
* Cancels polling timer, removes from activeAgents and commitRetryCount.
|
||||
*/
|
||||
private cleanupAgentState(agentId: string): void {
|
||||
const active = this.activeAgents.get(agentId);
|
||||
if (active?.cancelPoll) active.cancelPoll();
|
||||
this.activeAgents.delete(agentId);
|
||||
this.outputBuffers.delete(agentId);
|
||||
this.commitRetryCount.delete(agentId);
|
||||
}
|
||||
|
||||
@@ -129,6 +127,15 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
|
||||
return (content) => {
|
||||
repo.insertChunk({ agentId, agentName, sessionNumber, content })
|
||||
.then(() => {
|
||||
if (this.eventBus) {
|
||||
this.eventBus.emit({
|
||||
type: 'agent:output' as const,
|
||||
timestamp: new Date(),
|
||||
payload: { agentId, stream: 'stdout', data: content },
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => log.warn({ agentId, err: err instanceof Error ? err.message : String(err) }, 'failed to persist log chunk'));
|
||||
};
|
||||
}
|
||||
@@ -301,7 +308,8 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
// 6. Spawn detached subprocess
|
||||
const { pid, outputFilePath, tailer } = this.processManager.spawnDetached(
|
||||
agentId, alias, command, args, cwd ?? agentCwd, processEnv, providerName, prompt,
|
||||
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId), this.outputBuffers),
|
||||
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId)),
|
||||
this.createLogChunkCallback(agentId, alias, 1),
|
||||
);
|
||||
|
||||
await this.repository.update(agentId, { pid, outputFilePath });
|
||||
@@ -452,7 +460,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
|
||||
const { pid, outputFilePath, tailer } = this.processManager.spawnDetached(
|
||||
agentId, agent.name, command, args, agentCwd, processEnv, provider.name, commitPrompt,
|
||||
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId), this.outputBuffers),
|
||||
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId)),
|
||||
this.createLogChunkCallback(agentId, agent.name, commitSessionNumber),
|
||||
);
|
||||
|
||||
@@ -625,7 +633,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
|
||||
const { pid, outputFilePath, tailer } = this.processManager.spawnDetached(
|
||||
agentId, agent.name, command, args, agentCwd, processEnv, provider.name, prompt,
|
||||
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId), this.outputBuffers),
|
||||
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId)),
|
||||
this.createLogChunkCallback(agentId, agent.name, resumeSessionNumber),
|
||||
);
|
||||
|
||||
@@ -666,13 +674,6 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
return this.outputHandler.getPendingQuestions(agentId, this.activeAgents.get(agentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buffered output for an agent.
|
||||
*/
|
||||
getOutputBuffer(agentId: string): string[] {
|
||||
return this.outputHandler.getOutputBufferCopy(this.outputBuffers, agentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an agent and clean up all associated resources.
|
||||
*/
|
||||
@@ -759,7 +760,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
||||
const reconcileLogChunkRepo = this.logChunkRepository;
|
||||
await this.cleanupManager.reconcileAfterRestart(
|
||||
this.activeAgents,
|
||||
(agentId, event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId), this.outputBuffers),
|
||||
(agentId, event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId)),
|
||||
(agentId, rawOutput, provider) => this.outputHandler.processAgentOutput(agentId, rawOutput, provider, (alias) => this.processManager.getAgentWorkdir(alias)),
|
||||
(agentId, pid) => {
|
||||
const { cancel } = this.processManager.pollForCompletion(
|
||||
|
||||
Reference in New Issue
Block a user