fix(coding-agent): add settled agent lifecycle event

closes #6363
This commit is contained in:
Mario Zechner
2026-07-09 12:40:52 +02:00
parent 050b8176bf
commit e9fa5a68a1
14 changed files with 272 additions and 32 deletions
@@ -131,6 +131,7 @@ export type AgentSessionEvent =
messages: AgentMessage[];
willRetry: boolean;
}
| { type: "agent_settled" }
| {
type: "queue_update";
steering: readonly string[];
@@ -275,6 +276,9 @@ export class AgentSession {
// Event subscription state
private _unsubscribeAgent?: () => void;
private _eventListeners: AgentSessionEventListener[] = [];
private _isAgentRunActive = false;
private _idleWaitPromise: Promise<void> | undefined;
private _resolveIdleWait: (() => void) | undefined;
/** Tracks pending steering messages for UI display. Removed when delivered. */
private _steeringMessages: string[] = [];
@@ -508,6 +512,35 @@ export class AgentSession {
});
}
private _getIdleWaitPromise(): Promise<void> {
if (!this._idleWaitPromise) {
this._idleWaitPromise = new Promise((resolve) => {
this._resolveIdleWait = resolve;
});
}
return this._idleWaitPromise;
}
private _resolveIdleWaitIfIdle(): void {
if (this._isAgentRunActive || !this._resolveIdleWait) {
return;
}
const resolve = this._resolveIdleWait;
this._idleWaitPromise = undefined;
this._resolveIdleWait = undefined;
resolve();
}
private async _emitAgentSettled(): Promise<void> {
this._isAgentRunActive = false;
try {
await this._extensionRunner.emit({ type: "agent_settled" });
this._emit({ type: "agent_settled" });
} finally {
this._resolveIdleWaitIfIdle();
}
}
// Track last assistant message for auto-compaction check
private _lastAssistantMessage: AssistantMessage | undefined = undefined;
@@ -801,9 +834,14 @@ export class AgentSession {
return this.agent.state.thinkingLevel;
}
/** Whether agent is currently streaming a response */
/** Whether the session is currently processing an agent run or post-run continuation. */
get isStreaming(): boolean {
return this.agent.state.isStreaming;
return this._isAgentRunActive;
}
/** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */
get isIdle(): boolean {
return !this._isAgentRunActive;
}
/** Current effective system prompt (includes any per-turn extension modifications) */
@@ -983,6 +1021,7 @@ export class AgentSession {
// =========================================================================
private async _runAgentPrompt(messages: AgentMessage | AgentMessage[]): Promise<void> {
this._isAgentRunActive = true;
try {
await this.agent.prompt(messages);
while (await this._handlePostAgentRun()) {
@@ -991,6 +1030,7 @@ export class AgentSession {
} finally {
this._systemPromptOverride = undefined;
this._flushPendingBashMessages();
await this._emitAgentSettled();
}
}
@@ -1461,7 +1501,14 @@ export class AgentSession {
async abort(): Promise<void> {
this.abortRetry();
this.agent.abort();
await this.agent.waitForIdle();
await this.waitForIdle();
}
async waitForIdle(): Promise<void> {
if (this.isIdle) {
return;
}
await this._getIdleWaitPromise();
}
// =========================================================================
@@ -2305,7 +2352,7 @@ export class AgentSession {
},
{
getModel: () => this.model,
isIdle: () => !this.isStreaming,
isIdle: () => this.isIdle,
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
getSignal: () => this.agent.signal,
abort: () => {
@@ -22,6 +22,7 @@ export { ExtensionRunner } from "./runner.ts";
export type {
AfterProviderResponseEvent,
AgentEndEvent,
AgentSettledEvent,
AgentStartEvent,
// Re-exports
AgentToolResult,
@@ -705,6 +705,11 @@ export interface AgentEndEvent {
messages: AgentMessage[];
}
/** Fired after an agent run has fully settled and no automatic retry, compaction, or queued continuation will run. */
export interface AgentSettledEvent {
type: "agent_settled";
}
/** Fired at the start of each turn */
export interface TurnStartEvent {
type: "turn_start";
@@ -1021,6 +1026,7 @@ export type ExtensionEvent =
| BeforeAgentStartEvent
| AgentStartEvent
| AgentEndEvent
| AgentSettledEvent
| TurnStartEvent
| TurnEndEvent
| MessageStartEvent
@@ -1188,6 +1194,7 @@ export interface ExtensionAPI {
on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
on(event: "agent_settled", handler: ExtensionHandler<AgentSettledEvent>): void;
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;
on(event: "message_start", handler: ExtensionHandler<MessageStartEvent>): void;
+1
View File
@@ -32,6 +32,7 @@ export { areExperimentalFeaturesEnabled } from "./experimental.ts";
// Extensions system
export {
type AgentEndEvent,
type AgentSettledEvent,
type AgentStartEvent,
type AgentToolResult,
type AgentToolUpdateCallback,