feat: return agent snapshots from cancel and clear-attention RPCs
Convert cancel_agent_request and clear_agent_attention from fire-and-forget to request/response RPCs that return the authoritative agent snapshot. This enables client-side self-healing when agent_update messages are missed (e.g. agent appears stuck running when it isn't).
This commit is contained in:
@@ -1187,8 +1187,28 @@ export class DaemonClient {
|
||||
}
|
||||
}
|
||||
|
||||
clearAgentAttention(agentId: string | string[]): void {
|
||||
this.sendSessionMessage({ type: "clear_agent_attention", agentId });
|
||||
async clearAgentAttention(agentId: string | string[]): Promise<void> {
|
||||
const requestId = this.createRequestId();
|
||||
const message = SessionInboundMessageSchema.parse({
|
||||
type: "clear_agent_attention",
|
||||
agentId,
|
||||
requestId,
|
||||
});
|
||||
await this.sendRequest({
|
||||
requestId,
|
||||
message,
|
||||
timeout: 15000,
|
||||
options: { skipQueue: true },
|
||||
select: (msg) => {
|
||||
if (msg.type !== "clear_agent_attention_response") {
|
||||
return null;
|
||||
}
|
||||
if (msg.payload.requestId !== requestId) {
|
||||
return null;
|
||||
}
|
||||
return msg.payload;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
sendHeartbeat(params: {
|
||||
@@ -1692,7 +1712,27 @@ export class DaemonClient {
|
||||
}
|
||||
|
||||
async cancelAgent(agentId: string): Promise<void> {
|
||||
this.sendSessionMessage({ type: "cancel_agent_request", agentId });
|
||||
const requestId = this.createRequestId();
|
||||
const message = SessionInboundMessageSchema.parse({
|
||||
type: "cancel_agent_request",
|
||||
agentId,
|
||||
requestId,
|
||||
});
|
||||
await this.sendRequest({
|
||||
requestId,
|
||||
message,
|
||||
timeout: 15000,
|
||||
options: { skipQueue: true },
|
||||
select: (msg) => {
|
||||
if (msg.type !== "cancel_agent_response") {
|
||||
return null;
|
||||
}
|
||||
if (msg.payload.requestId !== requestId) {
|
||||
return null;
|
||||
}
|
||||
return msg.payload;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async setAgentMode(agentId: string, modeId: string): Promise<void> {
|
||||
|
||||
@@ -1752,7 +1752,7 @@ export class Session {
|
||||
break;
|
||||
|
||||
case "cancel_agent_request":
|
||||
await this.handleCancelAgentRequest(msg.agentId);
|
||||
await this.handleCancelAgentRequest(msg.agentId, msg.requestId);
|
||||
break;
|
||||
|
||||
case "restart_server_request":
|
||||
@@ -1933,7 +1933,7 @@ export class Session {
|
||||
break;
|
||||
|
||||
case "clear_agent_attention":
|
||||
await this.handleClearAgentAttention(msg.agentId);
|
||||
await this.handleClearAgentAttention(msg.agentId, msg.requestId);
|
||||
break;
|
||||
|
||||
case "client_heartbeat":
|
||||
@@ -3189,11 +3189,23 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleCancelAgentRequest(agentId: string): Promise<void> {
|
||||
private async handleCancelAgentRequest(agentId: string, requestId?: string): Promise<void> {
|
||||
this.sessionLogger.info({ agentId }, `Cancel request received for agent ${agentId}`);
|
||||
|
||||
try {
|
||||
await this.interruptAgentIfRunning(agentId);
|
||||
if (requestId) {
|
||||
const agent = this.agentManager.getAgent(agentId);
|
||||
const payload = agent ? await this.buildAgentPayload(agent) : null;
|
||||
this.emit({
|
||||
type: "cancel_agent_response",
|
||||
payload: {
|
||||
requestId,
|
||||
agentId,
|
||||
agent: payload,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
|
||||
}
|
||||
@@ -3857,11 +3869,32 @@ export class Session {
|
||||
/**
|
||||
* Handle clearing agent attention flag
|
||||
*/
|
||||
private async handleClearAgentAttention(agentId: string | string[]): Promise<void> {
|
||||
private async handleClearAgentAttention(
|
||||
agentId: string | string[],
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
const agentIds = Array.isArray(agentId) ? agentId : [agentId];
|
||||
|
||||
try {
|
||||
await Promise.all(agentIds.map((id) => this.agentManager.clearAgentAttention(id)));
|
||||
if (requestId) {
|
||||
const agents = (
|
||||
await Promise.all(
|
||||
agentIds.map(async (id) => {
|
||||
const agent = this.agentManager.getAgent(id);
|
||||
return agent ? this.buildAgentPayload(agent) : null;
|
||||
}),
|
||||
)
|
||||
).filter((payload): payload is NonNullable<typeof payload> => payload !== null);
|
||||
this.emit({
|
||||
type: "clear_agent_attention_response",
|
||||
payload: {
|
||||
requestId,
|
||||
agentId,
|
||||
agents,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.sessionLogger.error({ err: error, agentIds }, "Failed to clear agent attention");
|
||||
// Don't throw - this is not critical
|
||||
|
||||
@@ -876,6 +876,7 @@ export const RefreshAgentRequestMessageSchema = z.object({
|
||||
export const CancelAgentRequestMessageSchema = z.object({
|
||||
type: z.literal("cancel_agent_request"),
|
||||
agentId: z.string(),
|
||||
requestId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const RestartServerRequestMessageSchema = z.object({
|
||||
@@ -1307,6 +1308,7 @@ export const FileDownloadTokenRequestSchema = z.object({
|
||||
export const ClearAgentAttentionMessageSchema = z.object({
|
||||
type: z.literal("clear_agent_attention"),
|
||||
agentId: z.union([z.string(), z.array(z.string())]),
|
||||
requestId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ClientHeartbeatMessageSchema = z.object({
|
||||
@@ -2102,6 +2104,24 @@ export const FetchAgentTimelineResponseMessageSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const CancelAgentResponseMessageSchema = z.object({
|
||||
type: z.literal("cancel_agent_response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
agentId: z.string(),
|
||||
agent: AgentSnapshotPayloadSchema.nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ClearAgentAttentionResponseMessageSchema = z.object({
|
||||
type: z.literal("clear_agent_attention_response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
agentId: z.string().or(z.array(z.string())),
|
||||
agents: z.array(AgentSnapshotPayloadSchema),
|
||||
}),
|
||||
});
|
||||
|
||||
export const SendAgentMessageResponseMessageSchema = z.object({
|
||||
type: z.literal("send_agent_message_response"),
|
||||
payload: z.object({
|
||||
@@ -2771,6 +2791,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
ArchiveWorkspaceResponseMessageSchema,
|
||||
FetchAgentResponseMessageSchema,
|
||||
FetchAgentTimelineResponseMessageSchema,
|
||||
CancelAgentResponseMessageSchema,
|
||||
ClearAgentAttentionResponseMessageSchema,
|
||||
SendAgentMessageResponseMessageSchema,
|
||||
SetVoiceModeResponseMessageSchema,
|
||||
GetDaemonConfigResponseMessageSchema,
|
||||
@@ -2883,6 +2905,7 @@ export type FetchAgentResponseMessage = z.infer<typeof FetchAgentResponseMessage
|
||||
export type FetchAgentTimelineResponseMessage = z.infer<
|
||||
typeof FetchAgentTimelineResponseMessageSchema
|
||||
>;
|
||||
export type CancelAgentResponseMessage = z.infer<typeof CancelAgentResponseMessageSchema>;
|
||||
export type SendAgentMessageResponseMessage = z.infer<typeof SendAgentMessageResponseMessageSchema>;
|
||||
export type SetVoiceModeResponseMessage = z.infer<typeof SetVoiceModeResponseMessageSchema>;
|
||||
export type SetAgentModeResponseMessage = z.infer<typeof SetAgentModeResponseMessageSchema>;
|
||||
@@ -3048,6 +3071,9 @@ export type FileDownloadTokenResponse = z.infer<typeof FileDownloadTokenResponse
|
||||
export type RestartServerRequestMessage = z.infer<typeof RestartServerRequestMessageSchema>;
|
||||
export type ShutdownServerRequestMessage = z.infer<typeof ShutdownServerRequestMessageSchema>;
|
||||
export type ClearAgentAttentionMessage = z.infer<typeof ClearAgentAttentionMessageSchema>;
|
||||
export type ClearAgentAttentionResponseMessage = z.infer<
|
||||
typeof ClearAgentAttentionResponseMessageSchema
|
||||
>;
|
||||
export type ClientHeartbeatMessage = z.infer<typeof ClientHeartbeatMessageSchema>;
|
||||
export type ListCommandsRequest = z.infer<typeof ListCommandsRequestSchema>;
|
||||
export type ListCommandsResponse = z.infer<typeof ListCommandsResponseSchema>;
|
||||
|
||||
Reference in New Issue
Block a user