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 {
|
async clearAgentAttention(agentId: string | string[]): Promise<void> {
|
||||||
this.sendSessionMessage({ type: "clear_agent_attention", agentId });
|
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: {
|
sendHeartbeat(params: {
|
||||||
@@ -1692,7 +1712,27 @@ export class DaemonClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async cancelAgent(agentId: string): Promise<void> {
|
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> {
|
async setAgentMode(agentId: string, modeId: string): Promise<void> {
|
||||||
|
|||||||
@@ -1752,7 +1752,7 @@ export class Session {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "cancel_agent_request":
|
case "cancel_agent_request":
|
||||||
await this.handleCancelAgentRequest(msg.agentId);
|
await this.handleCancelAgentRequest(msg.agentId, msg.requestId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "restart_server_request":
|
case "restart_server_request":
|
||||||
@@ -1933,7 +1933,7 @@ export class Session {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "clear_agent_attention":
|
case "clear_agent_attention":
|
||||||
await this.handleClearAgentAttention(msg.agentId);
|
await this.handleClearAgentAttention(msg.agentId, msg.requestId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "client_heartbeat":
|
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}`);
|
this.sessionLogger.info({ agentId }, `Cancel request received for agent ${agentId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.interruptAgentIfRunning(agentId);
|
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) {
|
} catch (error) {
|
||||||
this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
|
this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
|
||||||
}
|
}
|
||||||
@@ -3857,11 +3869,32 @@ export class Session {
|
|||||||
/**
|
/**
|
||||||
* Handle clearing agent attention flag
|
* 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];
|
const agentIds = Array.isArray(agentId) ? agentId : [agentId];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(agentIds.map((id) => this.agentManager.clearAgentAttention(id)));
|
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) {
|
} catch (error: any) {
|
||||||
this.sessionLogger.error({ err: error, agentIds }, "Failed to clear agent attention");
|
this.sessionLogger.error({ err: error, agentIds }, "Failed to clear agent attention");
|
||||||
// Don't throw - this is not critical
|
// Don't throw - this is not critical
|
||||||
|
|||||||
@@ -876,6 +876,7 @@ export const RefreshAgentRequestMessageSchema = z.object({
|
|||||||
export const CancelAgentRequestMessageSchema = z.object({
|
export const CancelAgentRequestMessageSchema = z.object({
|
||||||
type: z.literal("cancel_agent_request"),
|
type: z.literal("cancel_agent_request"),
|
||||||
agentId: z.string(),
|
agentId: z.string(),
|
||||||
|
requestId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RestartServerRequestMessageSchema = z.object({
|
export const RestartServerRequestMessageSchema = z.object({
|
||||||
@@ -1307,6 +1308,7 @@ export const FileDownloadTokenRequestSchema = z.object({
|
|||||||
export const ClearAgentAttentionMessageSchema = z.object({
|
export const ClearAgentAttentionMessageSchema = z.object({
|
||||||
type: z.literal("clear_agent_attention"),
|
type: z.literal("clear_agent_attention"),
|
||||||
agentId: z.union([z.string(), z.array(z.string())]),
|
agentId: z.union([z.string(), z.array(z.string())]),
|
||||||
|
requestId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ClientHeartbeatMessageSchema = z.object({
|
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({
|
export const SendAgentMessageResponseMessageSchema = z.object({
|
||||||
type: z.literal("send_agent_message_response"),
|
type: z.literal("send_agent_message_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
@@ -2771,6 +2791,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
|||||||
ArchiveWorkspaceResponseMessageSchema,
|
ArchiveWorkspaceResponseMessageSchema,
|
||||||
FetchAgentResponseMessageSchema,
|
FetchAgentResponseMessageSchema,
|
||||||
FetchAgentTimelineResponseMessageSchema,
|
FetchAgentTimelineResponseMessageSchema,
|
||||||
|
CancelAgentResponseMessageSchema,
|
||||||
|
ClearAgentAttentionResponseMessageSchema,
|
||||||
SendAgentMessageResponseMessageSchema,
|
SendAgentMessageResponseMessageSchema,
|
||||||
SetVoiceModeResponseMessageSchema,
|
SetVoiceModeResponseMessageSchema,
|
||||||
GetDaemonConfigResponseMessageSchema,
|
GetDaemonConfigResponseMessageSchema,
|
||||||
@@ -2883,6 +2905,7 @@ export type FetchAgentResponseMessage = z.infer<typeof FetchAgentResponseMessage
|
|||||||
export type FetchAgentTimelineResponseMessage = z.infer<
|
export type FetchAgentTimelineResponseMessage = z.infer<
|
||||||
typeof FetchAgentTimelineResponseMessageSchema
|
typeof FetchAgentTimelineResponseMessageSchema
|
||||||
>;
|
>;
|
||||||
|
export type CancelAgentResponseMessage = z.infer<typeof CancelAgentResponseMessageSchema>;
|
||||||
export type SendAgentMessageResponseMessage = z.infer<typeof SendAgentMessageResponseMessageSchema>;
|
export type SendAgentMessageResponseMessage = z.infer<typeof SendAgentMessageResponseMessageSchema>;
|
||||||
export type SetVoiceModeResponseMessage = z.infer<typeof SetVoiceModeResponseMessageSchema>;
|
export type SetVoiceModeResponseMessage = z.infer<typeof SetVoiceModeResponseMessageSchema>;
|
||||||
export type SetAgentModeResponseMessage = z.infer<typeof SetAgentModeResponseMessageSchema>;
|
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 RestartServerRequestMessage = z.infer<typeof RestartServerRequestMessageSchema>;
|
||||||
export type ShutdownServerRequestMessage = z.infer<typeof ShutdownServerRequestMessageSchema>;
|
export type ShutdownServerRequestMessage = z.infer<typeof ShutdownServerRequestMessageSchema>;
|
||||||
export type ClearAgentAttentionMessage = z.infer<typeof ClearAgentAttentionMessageSchema>;
|
export type ClearAgentAttentionMessage = z.infer<typeof ClearAgentAttentionMessageSchema>;
|
||||||
|
export type ClearAgentAttentionResponseMessage = z.infer<
|
||||||
|
typeof ClearAgentAttentionResponseMessageSchema
|
||||||
|
>;
|
||||||
export type ClientHeartbeatMessage = z.infer<typeof ClientHeartbeatMessageSchema>;
|
export type ClientHeartbeatMessage = z.infer<typeof ClientHeartbeatMessageSchema>;
|
||||||
export type ListCommandsRequest = z.infer<typeof ListCommandsRequestSchema>;
|
export type ListCommandsRequest = z.infer<typeof ListCommandsRequestSchema>;
|
||||||
export type ListCommandsResponse = z.infer<typeof ListCommandsResponseSchema>;
|
export type ListCommandsResponse = z.infer<typeof ListCommandsResponseSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user