5 Commits

Author SHA1 Message Date
jiang 22afdbf2e8 fix(chat): 移除旧代码设计
Build Push and Deploy / docker-image (push) Successful in 3m42s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped
2026-06-08 20:25:48 +08:00
jiang ed9828befe fix(chat): hide actions while streaming
Build Push and Deploy / deploy-fallback-log (push) Has been cancelled
Build Push and Deploy / docker-image (push) Has been cancelled
2026-06-08 20:16:58 +08:00
jiang 968d798a2a fix(chat): hide raw permission metadata
Build Push and Deploy / docker-image (push) Failing after 42s
Build Push and Deploy / deploy-fallback-log (push) Successful in 0s
2026-06-08 20:12:08 +08:00
jiang 7da0ed0e39 fix(chat): mark aborted permissions
Build Push and Deploy / docker-image (push) Successful in 1m1s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped
2026-06-08 19:54:25 +08:00
jiang 166b45e529 fix(chat): normalize loaded messages
Build Push and Deploy / docker-image (push) Successful in 1m34s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped
2026-06-08 19:47:13 +08:00
10 changed files with 159 additions and 69 deletions
+38 -50
View File
@@ -27,32 +27,6 @@ import VerifiedUserRounded from "@mui/icons-material/VerifiedUserRounded";
import type { PermissionReply } from "@/lib/chatStream"; import type { PermissionReply } from "@/lib/chatStream";
import type { Message } from "./GlobalChatbox.types"; import type { Message } from "./GlobalChatbox.types";
const formatMetadataValue = (value: unknown) => {
if (typeof value === "string") {
return value;
}
try {
return JSON.stringify(value);
} catch {
return "[unserializable]";
}
};
const truncateText = (value: string, maxLength: number) =>
value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
const formatMetadata = (metadata: Record<string, unknown>) => {
const entries = Object.entries(metadata)
.filter(([key]) => !["command", "path", "file", "directory"].includes(key))
.slice(0, 3);
if (!entries.length) {
return "";
}
return entries
.map(([key, value]) => `${key}: ${truncateText(formatMetadataValue(value), 64)}`)
.join("");
};
const getPermissionTitle = (permission: NonNullable<Message["permissions"]>[number]) => { const getPermissionTitle = (permission: NonNullable<Message["permissions"]>[number]) => {
if (permission.permission === "external_directory") return "访问工作区外目录"; if (permission.permission === "external_directory") return "访问工作区外目录";
if (permission.permission === "bash") return "执行终端命令"; if (permission.permission === "bash") return "执行终端命令";
@@ -63,15 +37,8 @@ const getPermissionTitle = (permission: NonNullable<Message["permissions"]>[numb
const getPermissionPrimaryValue = ( const getPermissionPrimaryValue = (
permission: NonNullable<Message["permissions"]>[number], permission: NonNullable<Message["permissions"]>[number],
) => { ) => {
const command = permission.metadata.command; if (typeof permission.target === "string" && permission.target.trim()) {
if (typeof command === "string" && command.trim()) { return permission.target.trim();
return command.trim();
}
for (const key of ["path", "file", "directory"]) {
const value = permission.metadata[key];
if (typeof value === "string" && value.trim()) {
return value.trim();
}
} }
return permission.patterns[0] ?? permission.permission; return permission.patterns[0] ?? permission.permission;
}; };
@@ -94,6 +61,7 @@ const getPermissionStatusLabel = (status: NonNullable<Message["permissions"]>[nu
if (status === "approved_always") return "已始终允许"; if (status === "approved_always") return "已始终允许";
if (status === "approved_once") return "已允许一次"; if (status === "approved_once") return "已允许一次";
if (status === "rejected") return "已拒绝"; if (status === "rejected") return "已拒绝";
if (status === "aborted") return "已中断";
if (status === "error") return "提交失败"; if (status === "error") return "提交失败";
if (status === "submitting") return "提交中"; if (status === "submitting") return "提交中";
return "等待确认"; return "等待确认";
@@ -109,6 +77,7 @@ const getPermissionStatusColor = (
if (status === "approved_once") return approvedOncePermissionColor; if (status === "approved_once") return approvedOncePermissionColor;
if (status === "approved_always") return theme.palette.success.main; if (status === "approved_always") return theme.palette.success.main;
if (status === "rejected" || status === "error") return theme.palette.error.main; if (status === "rejected" || status === "error") return theme.palette.error.main;
if (status === "aborted") return theme.palette.text.secondary;
return pendingPermissionColor; return pendingPermissionColor;
}; };
@@ -119,21 +88,24 @@ const getPermissionStatusTextColor = (
if (status === "approved_once") return "#006c78"; if (status === "approved_once") return "#006c78";
if (status === "approved_always") return theme.palette.success.dark; if (status === "approved_always") return theme.palette.success.dark;
if (status === "rejected" || status === "error") return theme.palette.error.main; if (status === "rejected" || status === "error") return theme.palette.error.main;
if (status === "aborted") return theme.palette.text.secondary;
return "#8a5a00"; return "#8a5a00";
}; };
const PermissionRequestCard = ({ const PermissionRequestCard = ({
permission, permission,
isRunning,
onReply, onReply,
}: { }: {
permission: NonNullable<Message["permissions"]>[number]; permission: NonNullable<Message["permissions"]>[number];
isRunning: boolean;
onReply: (requestId: string, reply: PermissionReply) => void; onReply: (requestId: string, reply: PermissionReply) => void;
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isPending = permission.status === "pending" || permission.status === "error"; const isPending =
const isSubmitting = permission.status === "submitting"; isRunning && (permission.status === "pending" || permission.status === "error");
const isSubmitting = isRunning && permission.status === "submitting";
const primaryValue = getPermissionPrimaryValue(permission); const primaryValue = getPermissionPrimaryValue(permission);
const metadataText = formatMetadata(permission.metadata);
const accentColor = getPermissionStatusColor(permission.status, theme); const accentColor = getPermissionStatusColor(permission.status, theme);
const statusTextColor = getPermissionStatusTextColor(permission.status, theme); const statusTextColor = getPermissionStatusTextColor(permission.status, theme);
const statusLabel = getPermissionStatusLabel(permission.status); const statusLabel = getPermissionStatusLabel(permission.status);
@@ -231,12 +203,6 @@ const PermissionRequestCard = ({
{primaryValue} {primaryValue}
</Typography> </Typography>
</Box> </Box>
{metadataText ? (
<Typography variant="caption" color="text.secondary" sx={{ wordBreak: "break-word" }}>
{metadataText}
</Typography>
) : null}
</Stack> </Stack>
{permission.error ? ( {permission.error ? (
@@ -363,7 +329,13 @@ export const PermissionRequestGroup = ({
const onceCount = permissions.filter((permission) => permission.status === "approved_once").length; const onceCount = permissions.filter((permission) => permission.status === "approved_once").length;
const alwaysCount = permissions.filter((permission) => permission.status === "approved_always").length; const alwaysCount = permissions.filter((permission) => permission.status === "approved_always").length;
const rejectedCount = permissions.filter((permission) => permission.status === "rejected").length; const rejectedCount = permissions.filter((permission) => permission.status === "rejected").length;
const pendingCount = permissions.length - onceCount - alwaysCount - rejectedCount; const abortedCount = permissions.filter((permission) => permission.status === "aborted").length;
const pendingCount = permissions.filter(
(permission) =>
permission.status === "pending" ||
permission.status === "submitting" ||
permission.status === "error",
).length;
const hasPendingPermissions = pendingCount > 0; const hasPendingPermissions = pendingCount > 0;
const [expanded, setExpanded] = React.useState(false); const [expanded, setExpanded] = React.useState(false);
const latestPermissions = permissions.slice(-3); const latestPermissions = permissions.slice(-3);
@@ -378,9 +350,24 @@ export const PermissionRequestGroup = ({
{ label: "允许一次", value: onceCount, color: getPermissionStatusColor("approved_once", theme), textColor: getPermissionStatusTextColor("approved_once", theme) }, { label: "允许一次", value: onceCount, color: getPermissionStatusColor("approved_once", theme), textColor: getPermissionStatusTextColor("approved_once", theme) },
{ label: "始终允许", value: alwaysCount, color: getPermissionStatusColor("approved_always", theme), textColor: getPermissionStatusTextColor("approved_always", theme) }, { label: "始终允许", value: alwaysCount, color: getPermissionStatusColor("approved_always", theme), textColor: getPermissionStatusTextColor("approved_always", theme) },
{ label: "拒绝", value: rejectedCount, color: getPermissionStatusColor("rejected", theme), textColor: getPermissionStatusTextColor("rejected", theme) }, { label: "拒绝", value: rejectedCount, color: getPermissionStatusColor("rejected", theme), textColor: getPermissionStatusTextColor("rejected", theme) },
{ label: "中断", value: abortedCount, color: getPermissionStatusColor("aborted", theme), textColor: getPermissionStatusTextColor("aborted", theme) },
]; ];
const chipColor = pendingCount > 0 ? getPermissionStatusColor("pending", theme) : rejectedCount > 0 ? getPermissionStatusColor("rejected", theme) : getPermissionStatusColor("approved_always", theme); const chipColor =
const chipTextColor = pendingCount > 0 ? getPermissionStatusTextColor("pending", theme) : rejectedCount > 0 ? getPermissionStatusTextColor("rejected", theme) : getPermissionStatusTextColor("approved_always", theme); pendingCount > 0
? getPermissionStatusColor("pending", theme)
: abortedCount > 0
? getPermissionStatusColor("aborted", theme)
: rejectedCount > 0
? getPermissionStatusColor("rejected", theme)
: getPermissionStatusColor("approved_always", theme);
const chipTextColor =
pendingCount > 0
? getPermissionStatusTextColor("pending", theme)
: abortedCount > 0
? getPermissionStatusTextColor("aborted", theme)
: rejectedCount > 0
? getPermissionStatusTextColor("rejected", theme)
: getPermissionStatusTextColor("approved_always", theme);
return ( return (
<Box <Box
@@ -549,12 +536,13 @@ export const PermissionRequestGroup = ({
variant="caption" variant="caption"
color="text.secondary" color="text.secondary"
noWrap noWrap
title={primaryValue}
sx={{ sx={{
display: "block", display: "block",
fontFamily: permission.permission === "bash" ? "monospace" : undefined, fontFamily: permission.permission === "bash" ? "monospace" : undefined,
}} }}
> >
{truncateText(primaryValue, 72)} {primaryValue}
</Typography> </Typography>
</Box> </Box>
<Chip <Chip
@@ -591,6 +579,7 @@ export const PermissionRequestGroup = ({
<PermissionRequestCard <PermissionRequestCard
key={permission.requestId} key={permission.requestId}
permission={permission} permission={permission}
isRunning={isRunning}
onReply={onReply} onReply={onReply}
/> />
))} ))}
@@ -605,6 +594,7 @@ export const PermissionRequestGroup = ({
<PermissionRequestCard <PermissionRequestCard
key={permission.requestId} key={permission.requestId}
permission={permission} permission={permission}
isRunning={isRunning}
onReply={onReply} onReply={onReply}
/> />
))} ))}
@@ -613,5 +603,3 @@ export const PermissionRequestGroup = ({
</Box> </Box>
); );
}; };
+3 -1
View File
@@ -39,6 +39,7 @@ import StopRounded from "@mui/icons-material/StopRounded";
type AgentTurnProps = { type AgentTurnProps = {
message: Message; message: Message;
isStreaming: boolean;
messageSpeechState: SpeechState; messageSpeechState: SpeechState;
onSpeak: (messageId: string, text: string) => void; onSpeak: (messageId: string, text: string) => void;
onPause: () => void; onPause: () => void;
@@ -54,6 +55,7 @@ type AgentTurnProps = {
export const AgentTurn = React.memo( export const AgentTurn = React.memo(
({ ({
message, message,
isStreaming,
messageSpeechState, messageSpeechState,
onSpeak, onSpeak,
onPause, onPause,
@@ -277,7 +279,7 @@ export const AgentTurn = React.memo(
</Stack> </Stack>
<AnimatePresence> <AnimatePresence>
{isHovered && ( {isHovered && !isStreaming && (
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.9, y: 5 }} initial={{ opacity: 0, scale: 0.9, y: 5 }}
animate={{ opacity: 1, scale: 1, y: 0 }} animate={{ opacity: 1, scale: 1, y: 0 }}
+6
View File
@@ -36,6 +36,7 @@ type AgentWorkspaceProps = {
type TurnListProps = { type TurnListProps = {
messages: Message[]; messages: Message[];
isStreaming: boolean;
speakingMessageId: string | null; speakingMessageId: string | null;
speechState: SpeechState; speechState: SpeechState;
onSpeak: (messageId: string, text: string) => void; onSpeak: (messageId: string, text: string) => void;
@@ -55,6 +56,7 @@ const sameMessages = (left: Message[], right: Message[]) =>
const TurnListInner = ({ const TurnListInner = ({
messages, messages,
isStreaming,
speakingMessageId, speakingMessageId,
speechState, speechState,
onSpeak, onSpeak,
@@ -73,6 +75,7 @@ const TurnListInner = ({
<AgentTurn <AgentTurn
key={message.id} key={message.id}
message={message} message={message}
isStreaming={isStreaming}
messageSpeechState={speakingMessageId === message.id ? speechState : "idle"} messageSpeechState={speakingMessageId === message.id ? speechState : "idle"}
onSpeak={onSpeak} onSpeak={onSpeak}
onPause={onPauseSpeech} onPause={onPauseSpeech}
@@ -93,6 +96,7 @@ const TurnList = React.memo(
TurnListInner, TurnListInner,
(prevProps, nextProps) => (prevProps, nextProps) =>
sameMessages(prevProps.messages, nextProps.messages) && sameMessages(prevProps.messages, nextProps.messages) &&
prevProps.isStreaming === nextProps.isStreaming &&
prevProps.speakingMessageId === nextProps.speakingMessageId && prevProps.speakingMessageId === nextProps.speakingMessageId &&
prevProps.speechState === nextProps.speechState && prevProps.speechState === nextProps.speechState &&
prevProps.onSpeak === nextProps.onSpeak && prevProps.onSpeak === nextProps.onSpeak &&
@@ -274,6 +278,7 @@ export const AgentWorkspace = ({
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}> <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TurnList <TurnList
messages={historyMessages} messages={historyMessages}
isStreaming={isStreaming}
speakingMessageId={speakingMessageId} speakingMessageId={speakingMessageId}
speechState={speechState} speechState={speechState}
onSpeak={onSpeak} onSpeak={onSpeak}
@@ -290,6 +295,7 @@ export const AgentWorkspace = ({
{streamingMessage ? ( {streamingMessage ? (
<TurnList <TurnList
messages={[streamingMessage]} messages={[streamingMessage]}
isStreaming={isStreaming}
speakingMessageId={speakingMessageId} speakingMessageId={speakingMessageId}
speechState={speechState} speechState={speechState}
onSpeak={onSpeak} onSpeak={onSpeak}
+2 -1
View File
@@ -33,6 +33,7 @@ export type AgentPermissionStatus =
| "approved_once" | "approved_once"
| "approved_always" | "approved_always"
| "rejected" | "rejected"
| "aborted"
| "error"; | "error";
export type AgentPermissionRequest = { export type AgentPermissionRequest = {
@@ -40,7 +41,7 @@ export type AgentPermissionRequest = {
sessionId: string; sessionId: string;
permission: string; permission: string;
patterns: string[]; patterns: string[];
metadata: Record<string, unknown>; target?: string;
always: string[]; always: string[];
tool?: { tool?: {
messageID: string; messageID: string;
@@ -0,0 +1,35 @@
import { cloneMessage } from "./GlobalChatbox.utils";
import type { Message } from "./GlobalChatbox.types";
describe("cloneMessage", () => {
it("normalizes persisted question and todo arrays", () => {
const message = {
id: "assistant-1",
role: "assistant",
content: "需要补充信息",
questions: [
{
requestId: "question-1",
sessionId: "session-1",
questions: [
{
header: "范围",
question: "请选择分析范围",
},
],
createdAt: 1,
status: "pending",
},
],
todos: {
sessionId: "session-1",
createdAt: 1,
},
} as unknown as Message;
const cloned = cloneMessage(message);
expect(cloned.questions?.[0]?.questions[0]?.options).toEqual([]);
expect(cloned.todos?.todos).toEqual([]);
});
});
+61 -2
View File
@@ -1,4 +1,8 @@
import type { Message } from "./GlobalChatbox.types"; import type { Message } from "./GlobalChatbox.types";
import type {
AgentQuestionRequest,
AgentTodoUpdate,
} from "@/lib/chatStream";
export const createId = () => export const createId = () =>
`${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -29,10 +33,65 @@ export const stripMarkdown = (md: string): string =>
.replace(/<[^>]+>/g, "") .replace(/<[^>]+>/g, "")
.trim(); .trim();
const normalizeQuestionRequests = (
questions: Message["questions"],
): Message["questions"] =>
Array.isArray(questions)
? questions.map((request) => ({
...request,
questions: Array.isArray(request.questions)
? request.questions.map((question) => ({
...question,
header: typeof question.header === "string" ? question.header : "",
question:
typeof question.question === "string" ? question.question : "",
options: Array.isArray(question.options)
? question.options.map((option) => ({
label:
typeof option.label === "string" ? option.label : "",
description:
typeof option.description === "string"
? option.description
: "",
}))
: [],
}))
: [],
answers: Array.isArray(request.answers)
? request.answers.map((answer) =>
Array.isArray(answer)
? answer.filter((item): item is string => typeof item === "string")
: [],
)
: undefined,
} satisfies AgentQuestionRequest))
: undefined;
const normalizeTodoUpdate = (todos: Message["todos"]): Message["todos"] => {
if (!todos) return undefined;
return {
...todos,
todos: Array.isArray(todos.todos)
? todos.todos.map((todo) => ({ ...todo }))
: [],
} satisfies AgentTodoUpdate;
};
export const cloneMessage = (message: Message): Message => ({ export const cloneMessage = (message: Message): Message => ({
...message, ...message,
progress: message.progress ? [...message.progress] : undefined, progress: Array.isArray(message.progress) ? [...message.progress] : undefined,
artifacts: message.artifacts ? [...message.artifacts] : undefined, artifacts: Array.isArray(message.artifacts) ? [...message.artifacts] : undefined,
permissions: Array.isArray(message.permissions)
? message.permissions.map((permission) => ({
...permission,
patterns: Array.isArray(permission.patterns)
? [...permission.patterns]
: [],
always: Array.isArray(permission.always) ? [...permission.always] : [],
}))
: undefined,
questions: normalizeQuestionRequests(message.questions),
todos: normalizeTodoUpdate(message.todos),
}); });
export const cloneMessages = (messages: Message[]) => messages.map(cloneMessage); export const cloneMessages = (messages: Message[]) => messages.map(cloneMessage);
@@ -115,7 +115,7 @@ export const upsertPermission = (
sessionId: event.sessionId, sessionId: event.sessionId,
permission: event.permission, permission: event.permission,
patterns: event.patterns, patterns: event.patterns,
metadata: event.metadata, target: event.target,
always: event.always, always: event.always,
tool: event.tool, tool: event.tool,
createdAt: event.createdAt, createdAt: event.createdAt,
@@ -364,7 +364,7 @@ export const normalizeSessionTodos = (
return changed ? nextMessages : messages; return changed ? nextMessages : messages;
}; };
export const rejectOpenPermissionsAfterAbort = ( export const abortOpenPermissionsAfterAbort = (
permissions: AgentPermissionRequest[] | undefined, permissions: AgentPermissionRequest[] | undefined,
) => { ) => {
if (!permissions?.length) return permissions; if (!permissions?.length) return permissions;
@@ -380,7 +380,7 @@ export const rejectOpenPermissionsAfterAbort = (
changed = true; changed = true;
return { return {
...permission, ...permission,
status: "rejected" as const, status: "aborted" as const,
repliedAt: Date.now(), repliedAt: Date.now(),
error: undefined, error: undefined,
}; };
@@ -415,12 +415,12 @@ export const rejectOpenQuestionsAfterAbort = (
export const finalizeAssistantMessageAfterAbort = (message: Message): Message => { export const finalizeAssistantMessageAfterAbort = (message: Message): Message => {
const completedProgress = completeRunningProgress(message.progress); const completedProgress = completeRunningProgress(message.progress);
const cancelledTodos = cancelRunningTodos(message.todos); const cancelledTodos = cancelRunningTodos(message.todos);
const rejectedPermissions = rejectOpenPermissionsAfterAbort(message.permissions); const abortedPermissions = abortOpenPermissionsAfterAbort(message.permissions);
const rejectedQuestions = rejectOpenQuestionsAfterAbort(message.questions); const rejectedQuestions = rejectOpenQuestionsAfterAbort(message.questions);
const hasVisibleOutput = const hasVisibleOutput =
message.content.trim().length > 0 || message.content.trim().length > 0 ||
Boolean(message.artifacts?.length) || Boolean(message.artifacts?.length) ||
Boolean(rejectedPermissions?.length) || Boolean(abortedPermissions?.length) ||
Boolean(rejectedQuestions?.length) || Boolean(rejectedQuestions?.length) ||
Boolean(completedProgress?.length) || Boolean(completedProgress?.length) ||
Boolean(cancelledTodos); Boolean(cancelledTodos);
@@ -434,7 +434,7 @@ export const finalizeAssistantMessageAfterAbort = (message: Message): Message =>
content: message.content || "⚠️ **请求已中断**", content: message.content || "⚠️ **请求已中断**",
isError: true, isError: true,
progress: completedProgress, progress: completedProgress,
permissions: rejectedPermissions, permissions: abortedPermissions,
questions: rejectedQuestions, questions: rejectedQuestions,
todos: cancelledTodos, todos: cancelledTodos,
}; };
@@ -454,4 +454,3 @@ export const createAssistantMessage = (): Message => ({
role: "assistant", role: "assistant",
content: "", content: "",
}); });
@@ -99,7 +99,7 @@ describe("useAgentChatSession actions", () => {
requestId: "perm-1", requestId: "perm-1",
permission: "bash", permission: "bash",
patterns: ["rm *"], patterns: ["rm *"],
metadata: { command: "rm tmp.txt" }, target: "rm tmp.txt",
always: ["rm *"], always: ["rm *"],
createdAt: 123, createdAt: 123,
}); });
@@ -163,7 +163,7 @@ describe("useAgentChatSession actions", () => {
requestId: "perm-abort", requestId: "perm-abort",
permission: "bash", permission: "bash",
patterns: ["npm test"], patterns: ["npm test"],
metadata: { command: "npm test" }, target: "npm test",
always: ["npm test"], always: ["npm test"],
createdAt: 1002, createdAt: 1002,
} satisfies StreamEvent); } satisfies StreamEvent);
@@ -238,7 +238,7 @@ describe("useAgentChatSession actions", () => {
permissions: [ permissions: [
expect.objectContaining({ expect.objectContaining({
requestId: "perm-abort", requestId: "perm-abort",
status: "rejected", status: "aborted",
repliedAt: expect.any(Number), repliedAt: expect.any(Number),
error: undefined, error: undefined,
}), }),
+2 -2
View File
@@ -186,7 +186,7 @@ describe("streamAgentChat", () => {
apiFetch.mockResolvedValue({ apiFetch.mockResolvedValue({
ok: true, ok: true,
body: makeStream([ body: makeStream([
'event: permission_request\ndata: {"session_id":"s1","request_id":"perm-1","permission":"bash","patterns":["rm *"],"metadata":{"command":"rm tmp.txt"},"always":["rm *"],"created_at":123}\n\n', 'event: permission_request\ndata: {"session_id":"s1","request_id":"perm-1","permission":"bash","patterns":["rm *"],"target":"rm tmp.txt","always":["rm *"],"created_at":123}\n\n',
'event: permission_response\ndata: {"session_id":"s1","request_id":"perm-1","reply":"reject"}\n\n', 'event: permission_response\ndata: {"session_id":"s1","request_id":"perm-1","reply":"reject"}\n\n',
]), ]),
}); });
@@ -205,7 +205,7 @@ describe("streamAgentChat", () => {
requestId: "perm-1", requestId: "perm-1",
permission: "bash", permission: "bash",
patterns: ["rm *"], patterns: ["rm *"],
metadata: { command: "rm tmp.txt" }, target: "rm tmp.txt",
always: ["rm *"], always: ["rm *"],
tool: undefined, tool: undefined,
createdAt: 123, createdAt: 123,
+3 -3
View File
@@ -98,7 +98,7 @@ export type StreamEvent =
requestId: string; requestId: string;
permission: string; permission: string;
patterns: string[]; patterns: string[];
metadata: Record<string, unknown>; target?: string;
always: string[]; always: string[];
tool?: { tool?: {
messageID: string; messageID: string;
@@ -296,7 +296,7 @@ const emitParsedStreamEvent = (
request_id?: string; request_id?: string;
permission?: string; permission?: string;
patterns?: unknown; patterns?: unknown;
metadata?: unknown; target?: string;
always?: unknown; always?: unknown;
created_at?: number; created_at?: number;
reply?: PermissionReply; reply?: PermissionReply;
@@ -370,7 +370,7 @@ const emitParsedStreamEvent = (
patterns: Array.isArray(parsed.patterns) patterns: Array.isArray(parsed.patterns)
? parsed.patterns.filter((item): item is string => typeof item === "string") ? parsed.patterns.filter((item): item is string => typeof item === "string")
: [], : [],
metadata: isObjectRecord(parsed.metadata) ? parsed.metadata : {}, target: typeof parsed.target === "string" ? parsed.target : undefined,
always: Array.isArray(parsed.always) always: Array.isArray(parsed.always)
? parsed.always.filter((item): item is string => typeof item === "string") ? parsed.always.filter((item): item is string => typeof item === "string")
: [], : [],