= {
"1": "approve",
"2": "approve_for_session",
@@ -97,23 +140,31 @@ export function ApprovalDialog({
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
- }, [pendingApproval, disableActions, handleResponse]);
+ }, [pendingApproval, disableActions, handleResponse, feedbackMode]);
// if no pending approval request, do not render anything
if (!pendingApproval) return null;
const { approval, toolCall } = pendingApproval;
- const options = [
- { key: "approve", label: "Approve", pendingLabel: "Approving...", index: 1 },
- {
- key: "approve_for_session",
- label: "Approve for session",
- pendingLabel: "Approving...",
- index: 2,
- },
- { key: "reject", label: "Decline", pendingLabel: "Declining...", index: 3 },
- ] as const;
+ const sourceLabel = (() => {
+ if (approval.sourceDescription) return approval.sourceDescription;
+ const agentType = toolCall.subagentType;
+ const agentId = toolCall.subagentAgentId;
+ const idSuffix = agentId ? ` (${agentId})` : "";
+ if (approval.sourceKind === "background_agent") {
+ return agentType
+ ? `Background · ${agentType}${idSuffix}`
+ : `Background agent${idSuffix}`;
+ }
+ // Foreground sub-agent approvals (isSubagentOrigin)
+ if (toolCall.isSubagentOrigin) {
+ return agentType
+ ? `${agentType}${idSuffix}`
+ : `Sub-agent${idSuffix}`;
+ }
+ return null;
+ })();
return (
@@ -140,6 +191,11 @@ export function ApprovalDialog({
· {approval.sender}
)}
+ {sourceLabel && (
+
+ {sourceLabel}
+
+ )}
{/* Description */}
@@ -182,28 +238,104 @@ export function ApprovalDialog({
{/* Action buttons */}
- {options.map((option) => (
-
+
+ {/* Feedback input */}
+ {feedbackMode && (
+
+
+ rows={2}
+ />
+
+
+ Enter to submit · Shift+Enter for newline · Esc to cancel
+
+
+ Submit feedback
+
+
+
+ )}
diff --git a/web/src/features/chat/components/assistant-message.tsx b/web/src/features/chat/components/assistant-message.tsx
index 592f09415d..2a685125d9 100644
--- a/web/src/features/chat/components/assistant-message.tsx
+++ b/web/src/features/chat/components/assistant-message.tsx
@@ -204,7 +204,13 @@ const renderToolMessage = ({
isApprovalRequested
);
- return (
+ const subagentOriginLabel = toolCall.isSubagentOrigin
+ ? toolCall.subagentType
+ ? `${toolCall.subagentType} agent`
+ : "sub-agent"
+ : null;
+
+ const toolBlock = (
<>
) : null}
{shouldShowOutput ? (
@@ -312,6 +319,20 @@ const renderToolMessage = ({
) : null}
>
);
+
+ // Sub-agent origin: wrap in a visually demoted container with source label
+ if (subagentOriginLabel) {
+ return (
+
+
+ {subagentOriginLabel}
+
+ {toolBlock}
+
+ );
+ }
+
+ return toolBlock;
};
const ThinkToolBlock = ({
diff --git a/web/src/hooks/types.ts b/web/src/hooks/types.ts
index 8ed64d856a..01022c5cf2 100644
--- a/web/src/hooks/types.ts
+++ b/web/src/hooks/types.ts
@@ -19,7 +19,7 @@ export type { Session } from "../lib/api/models";
/**
* A single step recorded from a subagent's activity.
- * Accumulated as SubagentEvents arrive and rendered inside the parent Task tool call.
+ * Accumulated as SubagentEvents arrive and rendered inside the parent Agent tool call.
*/
export type SubagentStep =
| { kind: "thinking"; text: string }
@@ -114,6 +114,9 @@ export type LiveMessage = {
approved?: boolean;
reason?: string;
response?: unknown;
+ feedback?: string;
+ sourceKind?: "foreground_turn" | "background_agent" | null;
+ sourceDescription?: string | null;
};
question?: {
id: string;
@@ -124,10 +127,17 @@ export type LiveMessage = {
resolved?: boolean;
answers?: Record;
};
- /** Steps from a subagent (Task tool) — populated by SubagentEvent processing */
+ /** Steps from a subagent (Agent tool) — populated by SubagentEvent processing */
subagentSteps?: SubagentStep[];
/** Whether the subagent is still actively running */
subagentRunning?: boolean;
+ /** Built-in subagent type (coder / explore / plan) */
+ subagentType?: string;
+ /** Subagent instance ID */
+ subagentAgentId?: string;
+ /** True when this tool message was created from a sub-agent's ApprovalRequest
+ * (the tool_call_id belongs to the sub-agent, not the main agent) */
+ isSubagentOrigin?: boolean;
};
codeSnippet?: {
title: string;
diff --git a/web/src/hooks/useSessionStream.ts b/web/src/hooks/useSessionStream.ts
index c0f296d54e..0f2576510e 100644
--- a/web/src/hooks/useSessionStream.ts
+++ b/web/src/hooks/useSessionStream.ts
@@ -126,6 +126,7 @@ import {
type JsonRpcRequest,
type JsonRpcResponse,
type ApprovalRequestEvent,
+ type ApprovalRequestResolvedEvent,
type ApprovalResponseDecision,
type QuestionRequestEvent,
type SessionStatusPayload,
@@ -877,13 +878,19 @@ export function useSessionStream(
}
}, [resetStepState, setAwaitingFirstResponse]);
- // Process a SubagentEvent: accumulate inner events into parent Task tool's subagentSteps
+ // Process a SubagentEvent: accumulate inner events into parent Agent tool's subagentSteps
const processSubagentEvent = useCallback(
- (taskToolCallId: string, innerType: string, innerPayload: unknown) => {
+ (
+ parentToolCallId: string,
+ innerType: string,
+ innerPayload: unknown,
+ agentId?: string,
+ subagentType?: string,
+ ) => {
setMessages((prev) => {
- // Find the parent Task tool message by toolCallId
+ // Find the parent Agent tool message by toolCallId
const parentIdx = prev.findIndex(
- (msg) => msg.toolCall?.toolCallId === taskToolCallId,
+ (msg) => msg.toolCall?.toolCallId === parentToolCallId,
);
if (parentIdx === -1) return prev;
@@ -1025,6 +1032,11 @@ export function useSessionStream(
...parentMsg.toolCall!,
subagentSteps: steps,
subagentRunning: true,
+ // Preserve existing values; only set if provided and not yet set
+ subagentType:
+ parentMsg.toolCall?.subagentType ?? subagentType,
+ subagentAgentId:
+ parentMsg.toolCall?.subagentAgentId ?? agentId,
},
};
return next;
@@ -1343,7 +1355,7 @@ export function useSessionStream(
? messageStr || undefined
: undefined,
mediaParts: mediaParts.length > 0 ? mediaParts : undefined,
- // Mark subagent as complete when its parent Task tool receives result
+ // Mark subagent as complete when its parent Agent tool receives result
subagentRunning: msg.toolCall.subagentSteps
? false
: msg.toolCall.subagentRunning,
@@ -1385,7 +1397,7 @@ export function useSessionStream(
if (!isReplay) {
clearAwaitingFirstResponse();
}
- const payload = event.payload;
+ const payload = (event as ApprovalRequestEvent).payload;
const tc = currentToolCallsRef.current.get(payload.tool_call_id);
const approvalState = {
@@ -1397,6 +1409,8 @@ export function useSessionStream(
rpcMessageId,
submitted: false,
resolved: false,
+ sourceKind: payload.source_kind ?? null,
+ sourceDescription: payload.source_description ?? null,
};
if (tc) {
@@ -1418,6 +1432,10 @@ export function useSessionStream(
let messageId = tc?.messageId;
+ const approvalDisplay = payload.display?.length
+ ? payload.display
+ : undefined;
+
if (messageId) {
updateMessageById(messageId, (message) => {
if (!message.toolCall) {
@@ -1430,10 +1448,13 @@ export function useSessionStream(
...message.toolCall,
state: "approval-requested",
approval: approvalState,
+ // Show approval preview (diff/command) if tool has no display yet
+ display: message.toolCall.display ?? approvalDisplay,
},
};
});
} else {
+ const isSubagentOrigin = Boolean(payload.agent_id);
const fallbackMessageId = getNextMessageId("assistant");
const approvalMessage: LiveMessage = {
id: fallbackMessageId,
@@ -1445,6 +1466,12 @@ export function useSessionStream(
type: "tool-call" as ToolUIPart["type"],
state: "approval-requested",
approval: approvalState,
+ display: approvalDisplay,
+ ...(isSubagentOrigin && {
+ isSubagentOrigin: true,
+ subagentType: payload.subagent_type ?? undefined,
+ subagentAgentId: payload.agent_id ?? undefined,
+ }),
},
};
@@ -1474,7 +1501,8 @@ export function useSessionStream(
}
case "ApprovalRequestResolved": {
- const { request_id, response } = event.payload;
+ const { request_id, response, feedback } =
+ event.payload as ApprovalRequestResolvedEvent["payload"];
const pending = pendingApprovalRequestsRef.current.get(request_id);
let tc: ToolCallState | undefined;
@@ -1532,13 +1560,14 @@ export function useSessionStream(
}
}
+ const effectiveReason = reason ?? feedback ?? approval.reason;
const updatedApproval = {
...approval,
response,
resolved: true,
submitted: true,
approved,
- reason: reason ?? approval.reason,
+ reason: effectiveReason,
};
if (tc) {
@@ -1673,11 +1702,19 @@ export function useSessionStream(
case "SubagentEvent": {
const subPayload = (event as SubagentEventWire).payload;
- processSubagentEvent(
- subPayload.task_tool_call_id,
- subPayload.event.type,
- subPayload.event.payload,
- );
+ // Wire 1.6 uses parent_tool_call_id; fall back to legacy task_tool_call_id
+ const parentToolCallId =
+ subPayload.parent_tool_call_id ??
+ (subPayload as Record).task_tool_call_id as string | undefined;
+ if (parentToolCallId) {
+ processSubagentEvent(
+ parentToolCallId,
+ subPayload.event.type,
+ subPayload.event.payload,
+ subPayload.agent_id ?? undefined,
+ subPayload.subagent_type ?? undefined,
+ );
+ }
break;
}
@@ -2196,6 +2233,9 @@ export function useSessionStream(
result: {
request_id: pending.requestId ?? requestId,
response,
+ ...(response === "reject" && trimmedReason
+ ? { feedback: trimmedReason }
+ : {}),
},
};
diff --git a/web/src/hooks/wireTypes.ts b/web/src/hooks/wireTypes.ts
index 97579a5820..7654ca76a4 100644
--- a/web/src/hooks/wireTypes.ts
+++ b/web/src/hooks/wireTypes.ts
@@ -172,6 +172,13 @@ export type ApprovalRequestEvent = {
description: string;
sender: string;
tool_call_id: string;
+ /** Display blocks with preview content (diffs, shell commands) */
+ display?: Array<{ type: string; data: unknown }>;
+ source_kind?: "foreground_turn" | "background_agent" | null;
+ source_id?: string | null;
+ agent_id?: string | null;
+ subagent_type?: string | null;
+ source_description?: string | null;
};
};
@@ -180,6 +187,8 @@ export type ApprovalRequestResolvedEvent = {
payload: {
request_id: string;
response: unknown;
+ /** Feedback text provided with a rejection (Wire 1.6+) */
+ feedback?: string;
};
};
@@ -213,14 +222,16 @@ export type QuestionRequestEvent = {
};
/**
- * A SubagentEvent wraps an inner event produced by a subagent (Task tool).
+ * A SubagentEvent wraps an inner event produced by a subagent (Agent tool).
* The inner `event` field is a {type, payload} envelope that may itself be
* a SubagentEvent (for nested subagents).
*/
export type SubagentEventWire = {
type: "SubagentEvent";
payload: {
- task_tool_call_id: string;
+ parent_tool_call_id?: string | null;
+ agent_id?: string | null;
+ subagent_type?: string | null;
event: { type: string; payload: unknown };
};
};
@@ -314,6 +325,9 @@ export type ToolApprovalState = {
approved?: boolean;
reason?: string;
response?: unknown;
+ feedback?: string;
+ sourceKind?: "foreground_turn" | "background_agent" | null;
+ sourceDescription?: string | null;
};
// Content part for accumulated content