fix: use ACP protocol stopReason for proper agent completion detection
Replace unreliable 2-second timeout heuristic with proper protocol-based completion detection using PromptResponse.stopReason from ACP SDK. Changes: - Capture and handle PromptResponse.stopReason in sendPrompt() - Set agent status based on stop reason: end_turn → completed, refusal → failed, cancelled → ready - Remove 2-second timeout completion heuristic (was causing drift) - Update SessionStateMessageSchema to match AgentInfo null types This ensures agent completion is detected via the protocol spec, not manual polling/timing. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
co-authored by
Claude
Happy
parent
d883436395
commit
cba8780bca
@@ -225,7 +225,7 @@ export class AgentManager {
|
|||||||
this.notifySubscribers(agentId);
|
this.notifySubscribers(agentId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await agent.connection.prompt({
|
const response = await agent.connection.prompt({
|
||||||
sessionId: agent.sessionId!,
|
sessionId: agent.sessionId!,
|
||||||
prompt: [
|
prompt: [
|
||||||
{
|
{
|
||||||
@@ -234,6 +234,23 @@ export class AgentManager {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle completion based on stopReason from the protocol
|
||||||
|
console.log(`[Agent ${agentId}] Prompt completed with stopReason: ${response.stopReason}`);
|
||||||
|
|
||||||
|
if (response.stopReason === "end_turn") {
|
||||||
|
agent.status = "completed";
|
||||||
|
} else if (response.stopReason === "refusal") {
|
||||||
|
agent.status = "failed";
|
||||||
|
agent.error = "Agent refused to process the prompt";
|
||||||
|
} else if (response.stopReason === "cancelled") {
|
||||||
|
agent.status = "ready";
|
||||||
|
} else {
|
||||||
|
// max_tokens, max_turn_requests - still completed but may be truncated
|
||||||
|
agent.status = "completed";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notifyStatusChange(agentId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleAgentError(
|
this.handleAgentError(
|
||||||
agentId,
|
agentId,
|
||||||
@@ -435,29 +452,8 @@ export class AgentManager {
|
|||||||
console.log(`[Agent ${agentId}] Mode changed to: ${agent.currentModeId}`);
|
console.log(`[Agent ${agentId}] Mode changed to: ${agent.currentModeId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track completion based on final message state
|
|
||||||
// ACP protocol indicates completion when the last agent_message_chunk arrives
|
|
||||||
// We detect this by tracking a completion timer that fires if no more updates arrive
|
|
||||||
const updateType = update.update.sessionUpdate;
|
|
||||||
|
|
||||||
// Clear and reset completion timer on any update during processing
|
|
||||||
if (agent.status === "processing") {
|
|
||||||
if ((agent as any).completionTimer) {
|
|
||||||
clearTimeout((agent as any).completionTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new timer to mark as completed if no more updates arrive
|
|
||||||
// This is a heuristic - if no updates for 2 seconds, consider complete
|
|
||||||
(agent as any).completionTimer = setTimeout(() => {
|
|
||||||
if (agent.status === "processing") {
|
|
||||||
console.log(`[Agent ${agentId}] No more updates, marking as completed`);
|
|
||||||
agent.status = "completed";
|
|
||||||
this.notifyStatusChange(agentId);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log update for debugging
|
// Log update for debugging
|
||||||
|
const updateType = update.update.sessionUpdate;
|
||||||
console.log(
|
console.log(
|
||||||
`[Agent ${agentId}] Session update:`,
|
`[Agent ${agentId}] Session update:`,
|
||||||
updateType
|
updateType
|
||||||
|
|||||||
@@ -166,14 +166,14 @@ export const SessionStateMessageSchema = z.object({
|
|||||||
status: z.string(),
|
status: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
type: z.literal("claude"),
|
type: z.literal("claude"),
|
||||||
sessionId: z.string().optional(),
|
sessionId: z.string().nullable(),
|
||||||
error: z.string().optional(),
|
error: z.string().nullable(),
|
||||||
currentModeId: z.string().optional(),
|
currentModeId: z.string().nullable(),
|
||||||
availableModes: z.array(z.object({
|
availableModes: z.array(z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
})).optional(),
|
})).nullable(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
commands: z.array(
|
commands: z.array(
|
||||||
|
|||||||
Reference in New Issue
Block a user