diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index 86febabb..214bb403 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -206,7 +206,12 @@ export class Agent { * Continue from the current context without adding a new user message. * Used for retry after overflow recovery when context already has user message or tool results. */ - async continue() { + /** + * Continue from the current context without adding a new user message. + * Used for retry after overflow recovery when context already has user message or tool results. + * @param emitLastMessage If true, emit message_start/message_end for the last message + */ + async continue(emitLastMessage?: boolean) { const messages = this._state.messages; if (messages.length === 0) { throw new Error("No messages to continue from"); @@ -217,7 +222,7 @@ export class Agent { throw new Error(`Cannot continue from message role: ${lastMessage.role}`); } - await this._runAgentLoopContinue(); + await this._runAgentLoopContinue(emitLastMessage); } /** @@ -234,10 +239,10 @@ export class Agent { /** * Internal: Continue the agent loop from current context. */ - private async _runAgentLoopContinue() { + private async _runAgentLoopContinue(emitLastMessage?: boolean) { const { llmMessages, cfg } = await this._prepareRun(); - const events = this.transport.continue(llmMessages, cfg, this.abortController!.signal); + const events = this.transport.continue(llmMessages, cfg, this.abortController!.signal, emitLastMessage); await this._processEvents(events); } diff --git a/packages/agent/src/transports/AppTransport.ts b/packages/agent/src/transports/AppTransport.ts index 69b9af46..8748525c 100644 --- a/packages/agent/src/transports/AppTransport.ts +++ b/packages/agent/src/transports/AppTransport.ts @@ -380,7 +380,7 @@ export class AppTransport implements AgentTransport { } } - async *continue(messages: Message[], cfg: AgentRunConfig, signal?: AbortSignal) { + async *continue(messages: Message[], cfg: AgentRunConfig, signal?: AbortSignal, emitLastMessage?: boolean) { const authToken = await this.options.getAuthToken(); if (!authToken) { throw new Error("Auth token is required for AppTransport"); @@ -390,7 +390,7 @@ export class AppTransport implements AgentTransport { const context = this.buildContext(messages, cfg); const pc = this.buildLoopConfig(cfg); - for await (const ev of agentLoopContinue(context, pc, signal, streamFn as any)) { + for await (const ev of agentLoopContinue(context, pc, signal, streamFn as any, emitLastMessage)) { yield ev; } } diff --git a/packages/agent/src/transports/ProviderTransport.ts b/packages/agent/src/transports/ProviderTransport.ts index 024db0e4..09240f73 100644 --- a/packages/agent/src/transports/ProviderTransport.ts +++ b/packages/agent/src/transports/ProviderTransport.ts @@ -73,12 +73,12 @@ export class ProviderTransport implements AgentTransport { } } - async *continue(messages: Message[], cfg: AgentRunConfig, signal?: AbortSignal) { + async *continue(messages: Message[], cfg: AgentRunConfig, signal?: AbortSignal, emitLastMessage?: boolean) { const model = this.getModel(cfg); const context = this.buildContext(messages, cfg); const pc = this.buildLoopConfig(model, cfg); - for await (const ev of agentLoopContinue(context, pc, signal)) { + for await (const ev of agentLoopContinue(context, pc, signal, undefined, emitLastMessage)) { yield ev; } } diff --git a/packages/agent/src/transports/types.ts b/packages/agent/src/transports/types.ts index 736ba0c3..baed2b81 100644 --- a/packages/agent/src/transports/types.ts +++ b/packages/agent/src/transports/types.ts @@ -28,5 +28,10 @@ export interface AgentTransport { ): AsyncIterable; /** Continue from current context (no new user message) */ - continue(messages: Message[], config: AgentRunConfig, signal?: AbortSignal): AsyncIterable; + continue( + messages: Message[], + config: AgentRunConfig, + signal?: AbortSignal, + emitLastMessage?: boolean, + ): AsyncIterable; } diff --git a/packages/ai/src/agent/agent-loop.ts b/packages/ai/src/agent/agent-loop.ts index badb1bca..059cb666 100644 --- a/packages/ai/src/agent/agent-loop.ts +++ b/packages/ai/src/agent/agent-loop.ts @@ -40,11 +40,18 @@ export function agentLoop( * Used for retry after overflow - context already has user message or tool results. * Throws if the last message is not a user message or tool result. */ +/** + * Continue an agent loop from the current context without adding a new message. + * Used for retry after overflow - context already has user message or tool results. + * Throws if the last message is not a user message or tool result. + * @param emitLastMessage If true, emit message_start/message_end for the last message in context + */ export function agentLoopContinue( context: AgentContext, config: AgentLoopConfig, signal?: AbortSignal, streamFn?: typeof streamSimple, + emitLastMessage?: boolean, ): EventStream { // Validate that we can continue from this context const lastMessage = context.messages[context.messages.length - 1]; @@ -63,7 +70,12 @@ export function agentLoopContinue( stream.push({ type: "agent_start" }); stream.push({ type: "turn_start" }); - // No user message events - we're continuing from existing context + + // Optionally emit events for the last message (used when message was added outside the loop) + if (emitLastMessage) { + stream.push({ type: "message_start", message: lastMessage }); + stream.push({ type: "message_end", message: lastMessage }); + } await runLoop(currentContext, newMessages, config, signal, stream, streamFn); })(); diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 45c6f3c0..8839317d 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -602,8 +602,8 @@ export class AgentSession { message.display, message.details, ); - // Start a new turn - agent.continue() works because last message is user role - await this.agent.continue(); + // Start a new turn - emit message events for the hook message so TUI can render it + await this.agent.continue(true); } else { // Just append to agent state and session, no turn this.agent.appendMessage(appMessage);