Add active-turn steering (#3394)

* feat(agents): add active-turn steering

Let users choose whether an immediate prompt interrupts or joins the active turn. The daemon owns exact-turn admission, provider delivery, safe fallback, and canonical timeline identity while queueing remains client-side.

* fix(agents): stabilize steering history and replay QA

* fix(agents): preserve steering timeline order

* fix(agents): reconcile resumed provider history

* fix(agents): serialize steering with cancellation

* test(agents): prove Claude multi-tool steering

* test(agents): force explicit history hydration

* fix(agents): flush output before steer admission
This commit is contained in:
Mohamed Boudra
2026-08-16 13:58:40 +02:00
committed by GitHub
parent 16120ebff1
commit 42245d139a
69 changed files with 5203 additions and 269 deletions
+7
View File
@@ -748,6 +748,7 @@ export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [
type: z.literal("timeline"),
provider: AgentProviderSchema,
item: AgentTimelineItemPayloadSchema,
turnId: z.string().optional(),
}),
z.object({
type: z.literal("permission_requested"),
@@ -1138,11 +1139,15 @@ const ImageAttachmentSchema = z.object({
mimeType: z.string(), // e.g., "image/jpeg", "image/png"
});
export const ActiveTurnBehaviorSchema = z.enum(["interrupt", "steer"]);
export type ActiveTurnBehavior = z.infer<typeof ActiveTurnBehaviorSchema>;
export const SendAgentMessageSchema = z.object({
type: z.literal("send_agent_message"),
agentId: z.string(),
text: z.string(),
messageId: z.string().optional(), // Client-provided ID for deduplication
activeTurnBehavior: ActiveTurnBehaviorSchema.optional(),
images: z.array(ImageAttachmentSchema).optional(),
attachments: AgentAttachmentsSchema,
});
@@ -1295,6 +1300,7 @@ export const SendAgentMessageRequestSchema = z.object({
agentId: z.string(),
text: z.string(),
messageId: z.string().optional(), // Client-provided ID for deduplication
activeTurnBehavior: ActiveTurnBehaviorSchema.optional(),
images: z.array(ImageAttachmentSchema).optional(),
attachments: AgentAttachmentsSchema,
});
@@ -4035,6 +4041,7 @@ const AgentTimelineSeqRangeSchema = z.object({
export const AgentTimelineEntryPayloadSchema = z.object({
provider: AgentProviderSchema,
item: AgentTimelineItemPayloadSchema,
turnId: z.string().optional(),
timestamp: z.string(),
seqStart: z.number().int().nonnegative(),
seqEnd: z.number().int().nonnegative(),