feat: add permission request UI
This commit is contained in:
@@ -5,12 +5,14 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
abortAgentChat,
|
||||
forkAgentChat,
|
||||
replyAgentPermission,
|
||||
resumeAgentChatStream,
|
||||
streamAgentChat,
|
||||
} from "@/lib/chatStream";
|
||||
import type { AgentModel, StreamEvent } from "@/lib/chatStream";
|
||||
import type { AgentModel, PermissionReply, StreamEvent } from "@/lib/chatStream";
|
||||
import type {
|
||||
AgentArtifact,
|
||||
AgentPermissionRequest,
|
||||
BranchGroup,
|
||||
BranchTransition,
|
||||
ChatProgress,
|
||||
@@ -130,6 +132,41 @@ const completeRunningProgress = (progress: ChatProgress[] | undefined) =>
|
||||
};
|
||||
});
|
||||
|
||||
const upsertPermission = (
|
||||
permissions: AgentPermissionRequest[] | undefined,
|
||||
event: StreamEvent & { type: "permission_request" },
|
||||
) => {
|
||||
const next = [...(permissions ?? [])];
|
||||
const index = next.findIndex((item) => item.requestId === event.requestId);
|
||||
const nextItem: AgentPermissionRequest = {
|
||||
requestId: event.requestId,
|
||||
sessionId: event.sessionId,
|
||||
permission: event.permission,
|
||||
patterns: event.patterns,
|
||||
metadata: event.metadata,
|
||||
always: event.always,
|
||||
tool: event.tool,
|
||||
createdAt: event.createdAt,
|
||||
status: "pending",
|
||||
};
|
||||
if (index >= 0) {
|
||||
next[index] = {
|
||||
...next[index],
|
||||
...nextItem,
|
||||
status: next[index].status === "submitting" ? "submitting" : nextItem.status,
|
||||
};
|
||||
} else {
|
||||
next.push(nextItem);
|
||||
}
|
||||
return next;
|
||||
};
|
||||
|
||||
const toPermissionStatus = (reply: PermissionReply): AgentPermissionRequest["status"] => {
|
||||
if (reply === "always") return "approved_always";
|
||||
if (reply === "once") return "approved_once";
|
||||
return "rejected";
|
||||
};
|
||||
|
||||
const finalizeAssistantMessageAfterAbort = (message: Message): Message => {
|
||||
const completedProgress = completeRunningProgress(message.progress);
|
||||
const hasVisibleOutput =
|
||||
@@ -442,6 +479,38 @@ export const useAgentChatSession = ({
|
||||
assistantMessageId,
|
||||
appendArtifact,
|
||||
});
|
||||
} else if (event.type === "permission_request") {
|
||||
setMessages((prev) =>
|
||||
prev.map((message) =>
|
||||
message.id === assistantMessageId
|
||||
? {
|
||||
...message,
|
||||
permissions: upsertPermission(message.permissions, event),
|
||||
}
|
||||
: message,
|
||||
),
|
||||
);
|
||||
} else if (event.type === "permission_response") {
|
||||
setMessages((prev) =>
|
||||
prev.map((message) => {
|
||||
if (message.id !== assistantMessageId || !message.permissions?.length) {
|
||||
return message;
|
||||
}
|
||||
return {
|
||||
...message,
|
||||
permissions: message.permissions.map((permission) =>
|
||||
permission.requestId === event.requestId
|
||||
? {
|
||||
...permission,
|
||||
status: toPermissionStatus(event.reply),
|
||||
repliedAt: Date.now(),
|
||||
error: undefined,
|
||||
}
|
||||
: permission,
|
||||
),
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else if (event.type === "session_title") {
|
||||
const nextTitle = event.title.trim();
|
||||
if (nextTitle && !isSessionTitleManuallyEditedRef.current) {
|
||||
@@ -647,6 +716,75 @@ export const useAgentChatSession = ({
|
||||
cancelPromiseRef.current = trackedCancelPromise;
|
||||
}, [getLastAssistantMessageId]);
|
||||
|
||||
const replyPermission = useCallback(
|
||||
async (requestId: string, reply: PermissionReply) => {
|
||||
const target = messagesRef.current
|
||||
.flatMap((message) => message.permissions ?? [])
|
||||
.find((permission) => permission.requestId === requestId);
|
||||
if (!target || target.status === "submitting") {
|
||||
return;
|
||||
}
|
||||
|
||||
setMessages((prev) =>
|
||||
prev.map((message) =>
|
||||
!message.permissions?.some((permission) => permission.requestId === requestId)
|
||||
? message
|
||||
: {
|
||||
...message,
|
||||
permissions: message.permissions.map((permission) =>
|
||||
permission.requestId === requestId
|
||||
? { ...permission, status: "submitting", error: undefined }
|
||||
: permission,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await replyAgentPermission(target.sessionId, requestId, reply);
|
||||
setMessages((prev) =>
|
||||
prev.map((message) =>
|
||||
!message.permissions?.some((permission) => permission.requestId === requestId)
|
||||
? message
|
||||
: {
|
||||
...message,
|
||||
permissions: message.permissions.map((permission) =>
|
||||
permission.requestId === requestId
|
||||
? {
|
||||
...permission,
|
||||
status: toPermissionStatus(reply),
|
||||
repliedAt: Date.now(),
|
||||
error: undefined,
|
||||
}
|
||||
: permission,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
setMessages((prev) =>
|
||||
prev.map((message) =>
|
||||
!message.permissions?.some((permission) => permission.requestId === requestId)
|
||||
? message
|
||||
: {
|
||||
...message,
|
||||
permissions: message.permissions.map((permission) =>
|
||||
permission.requestId === requestId
|
||||
? {
|
||||
...permission,
|
||||
status: "error",
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
}
|
||||
: permission,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const createSession = useCallback(() => {
|
||||
if (isHydrating || isStreaming) return;
|
||||
|
||||
@@ -982,6 +1120,7 @@ export const useAgentChatSession = ({
|
||||
editAndResubmit,
|
||||
cycleBranch,
|
||||
abort,
|
||||
replyPermission,
|
||||
createSession,
|
||||
renameSession,
|
||||
removeSession,
|
||||
|
||||
Reference in New Issue
Block a user