@@ -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;
|
||||
|
||||
@@ -32,6 +32,7 @@ export { areExperimentalFeaturesEnabled } from "./experimental.ts";
|
||||
// Extensions system
|
||||
export {
|
||||
type AgentEndEvent,
|
||||
type AgentSettledEvent,
|
||||
type AgentStartEvent,
|
||||
type AgentToolResult,
|
||||
type AgentToolUpdateCallback,
|
||||
|
||||
Reference in New Issue
Block a user