forked from claude-code-best/claude-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUserToolSuccessMessage.tsx
More file actions
131 lines (121 loc) · 4.6 KB
/
Copy pathUserToolSuccessMessage.tsx
File metadata and controls
131 lines (121 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { feature } from 'bun:bundle';
import figures from 'figures';
import * as React from 'react';
import { SentryErrorBoundary } from 'src/components/SentryErrorBoundary.js';
import { Box, Text, useTheme } from '@anthropic/ink';
import { useAppState } from '../../../state/AppState.js';
import { filterToolProgressMessages, type Tool, type Tools } from '../../../Tool.js';
import type { NormalizedUserMessage, ProgressMessage } from '../../../types/message.js';
import {
deleteClassifierApproval,
getClassifierApproval,
getYoloClassifierApproval,
} from '../../../utils/classifierApprovals.js';
import type { buildMessageLookups } from '../../../utils/messages.js';
import { MessageResponse } from '../../MessageResponse.js';
import { HookProgressMessage } from '../HookProgressMessage.js';
type Props = {
message: NormalizedUserMessage;
lookups: ReturnType<typeof buildMessageLookups>;
toolUseID: string;
progressMessagesForMessage: ProgressMessage[];
style?: 'condensed';
tool?: Tool;
tools: Tools;
verbose: boolean;
width: number | string;
isTranscriptMode?: boolean;
shouldCollapseDiffs?: boolean;
};
export function UserToolSuccessMessage({
message,
lookups,
toolUseID,
progressMessagesForMessage,
style,
tool,
tools,
verbose,
width,
isTranscriptMode,
shouldCollapseDiffs,
}: Props): React.ReactNode {
const [theme] = useTheme();
// Always call hook unconditionally; feature gate applied to the value.
const isBriefOnlyState = useAppState(s => s.isBriefOnly);
const isBriefOnly = feature('KAIROS') || feature('KAIROS_BRIEF') ? isBriefOnlyState : false;
// Capture classifier approval once on mount, then delete from Map to prevent linear growth.
// useState lazy initializer ensures the value persists across re-renders.
const [classifierRule] = React.useState(() => getClassifierApproval(toolUseID));
const [yoloReason] = React.useState(() => getYoloClassifierApproval(toolUseID));
React.useEffect(() => {
deleteClassifierApproval(toolUseID);
}, [toolUseID]);
if (!message.toolUseResult || !tool) {
return null;
}
// Resumed transcripts deserialize toolUseResult via raw JSON.parse with no
// validation (parseJSONL). A partial/corrupt/old-format result crashes
// renderToolResultMessage on first field access (anthropics/claude-code#39817).
// Validate against outputSchema before rendering — mirrors CollapsedReadSearchContent.
const parsedOutput = tool.outputSchema?.safeParse(message.toolUseResult);
if (parsedOutput && !parsedOutput.success) {
return null;
}
const toolResult = parsedOutput?.data ?? message.toolUseResult;
// Collapse diff display for old messages (verbose/ctrl+o overrides)
const effectiveStyle = shouldCollapseDiffs && !verbose ? 'condensed' : style;
const renderedMessage =
tool.renderToolResultMessage?.(toolResult as never, filterToolProgressMessages(progressMessagesForMessage), {
style: effectiveStyle,
theme,
tools,
verbose,
isTranscriptMode,
isBriefOnly,
input: lookups.toolUseByToolUseID.get(toolUseID)?.input,
}) ?? null;
// Don't render anything if the tool result message is null
if (renderedMessage === null) {
return null;
}
// Tools that return '' from userFacingName opt out of tool chrome and
// render like plain assistant text. Skip the tool-result width constraint
// so MarkdownTable's SAFETY_MARGIN=4 (tuned for the assistant-text 2-col
// dot gutter) holds — otherwise tables wrap their box-drawing chars.
const rendersAsAssistantText = tool.userFacingName(undefined) === '';
return (
<Box flexDirection="column">
<Box flexDirection="column" width={rendersAsAssistantText ? undefined : width}>
{renderedMessage}
{feature('BASH_CLASSIFIER')
? classifierRule && (
<MessageResponse height={1}>
<Text dimColor>
<Text color="success">{figures.tick}</Text>
{' Auto-approved \u00b7 matched '}
{`"${classifierRule}"`}
</Text>
</MessageResponse>
)
: null}
{feature('TRANSCRIPT_CLASSIFIER')
? yoloReason && (
<MessageResponse height={1}>
<Text dimColor>Allowed by auto mode classifier</Text>
</MessageResponse>
)
: null}
</Box>
<SentryErrorBoundary>
<HookProgressMessage
hookEvent="PostToolUse"
lookups={lookups}
toolUseID={toolUseID}
verbose={verbose}
isTranscriptMode={isTranscriptMode}
/>
</SentryErrorBoundary>
</Box>
);
}