/ components / messages / nullRenderingAttachments.ts
nullRenderingAttachments.ts
 1  import type { Attachment } from 'src/utils/attachments.js'
 2  import type { Message, NormalizedMessage } from '../../types/message.js'
 3  
 4  /**
 5   * Attachment types that AttachmentMessage renders as `null` unconditionally
 6   * (no visible output regardless of runtime state). Messages.tsx filters these
 7   * out BEFORE the render cap / message count so invisible entries don't consume
 8   * the 200-message render budget (CC-724).
 9   *
10   * Sync is enforced by TypeScript: AttachmentMessage's switch `default:` branch
11   * asserts `attachment.type satisfies NullRenderingAttachmentType`. Adding a new
12   * Attachment type without either a case or an entry here will fail typecheck.
13   */
14  const NULL_RENDERING_TYPES = [
15    'hook_success',
16    'hook_additional_context',
17    'hook_cancelled',
18    'command_permissions',
19    'agent_mention',
20    'budget_usd',
21    'critical_system_reminder',
22    'edited_image_file',
23    'edited_text_file',
24    'opened_file_in_ide',
25    'output_style',
26    'plan_mode',
27    'plan_mode_exit',
28    'plan_mode_reentry',
29    'structured_output',
30    'team_context',
31    'todo_reminder',
32    'context_efficiency',
33    'deferred_tools_delta',
34    'mcp_instructions_delta',
35    'companion_intro',
36    'token_usage',
37    'ultrathink_effort',
38    'max_turns_reached',
39    'task_reminder',
40    'auto_mode',
41    'auto_mode_exit',
42    'output_token_usage',
43    'pen_mode_enter',
44    'pen_mode_exit',
45    'verify_plan_reminder',
46    'current_session_memory',
47    'compaction_reminder',
48    'date_change',
49  ] as const satisfies readonly Attachment['type'][]
50  
51  export type NullRenderingAttachmentType = (typeof NULL_RENDERING_TYPES)[number]
52  
53  const NULL_RENDERING_ATTACHMENT_TYPES: ReadonlySet<Attachment['type']> =
54    new Set(NULL_RENDERING_TYPES)
55  
56  /**
57   * True when this message is an attachment that AttachmentMessage renders as
58   * null with no visible output. Messages.tsx filters these out before counting
59   * and before applying the 200-message render cap, so invisible hook
60   * attachments (hook_success, hook_additional_context, hook_cancelled) don't
61   * inflate the "N messages" count or eat into the render budget (CC-724).
62   */
63  export function isNullRenderingAttachment(
64    msg: Message | NormalizedMessage,
65  ): boolean {
66    return (
67      msg.type === 'attachment' &&
68      NULL_RENDERING_ATTACHMENT_TYPES.has(msg.attachment.type)
69    )
70  }