source dump of claude code
1import type { UUID } from 'crypto'
2import type { FileHistorySnapshot } from 'src/utils/fileHistory.js'
3import type { ContentReplacementRecord } from 'src/utils/toolResultStorage.js'
4import type { AgentId } from './ids.js'
5import type { Message } from './message.js'
6import type { QueueOperationMessage } from './messageQueueTypes.js'
7
8export type SerializedMessage = Message & {
9 cwd: string
10 userType: string
11 entrypoint?: string // CLAUDE_CODE_ENTRYPOINT — distinguishes cli/sdk-ts/sdk-py/etc.
12 sessionId: string
13 timestamp: string
14 version: string
15 gitBranch?: string
16 slug?: string // Session slug for files like plans (used for resume)
17}
18
19export type LogOption = {
20 date: string
21 messages: SerializedMessage[]
22 fullPath?: string
23 value: number
24 created: Date
25 modified: Date
26 firstPrompt: string
27 messageCount: number
28 fileSize?: number // File size in bytes (for display)
29 isSidechain: boolean
30 isLite?: boolean // True for lite logs (messages not loaded)
31 sessionId?: string // Session ID for lite logs
32 teamName?: string // Team name if this is a spawned agent session
33 agentName?: string // Agent's custom name (from /rename or swarm)
34 agentColor?: string // Agent's color (from /rename or swarm)
35 agentSetting?: string // Agent definition used (from --agent flag or settings.agent)
36 isTeammate?: boolean // Whether this session was created by a swarm teammate
37 leafUuid?: UUID // If given, this uuid must appear in the DB
38 summary?: string // Optional conversation summary
39 customTitle?: string // Optional user-set custom title
40 tag?: string // Optional tag for the session (searchable in /resume)
41 fileHistorySnapshots?: FileHistorySnapshot[] // Optional file history snapshots
42 attributionSnapshots?: AttributionSnapshotMessage[] // Optional attribution snapshots
43 contextCollapseCommits?: ContextCollapseCommitEntry[] // Ordered — commit B may reference commit A's summary
44 contextCollapseSnapshot?: ContextCollapseSnapshotEntry // Last-wins — staged queue + spawn state
45 gitBranch?: string // Git branch at the end of the session
46 projectPath?: string // Original project directory path
47 prNumber?: number // GitHub PR number linked to this session
48 prUrl?: string // Full URL to the linked PR
49 prRepository?: string // Repository in "owner/repo" format
50 mode?: 'coordinator' | 'normal' // Session mode for coordinator/normal detection
51 worktreeSession?: PersistedWorktreeSession | null // Worktree state at session end (null = exited, undefined = never entered)
52 contentReplacements?: ContentReplacementRecord[] // Replacement decisions for resume reconstruction
53}
54
55export type SummaryMessage = {
56 type: 'summary'
57 leafUuid: UUID
58 summary: string
59}
60
61export type CustomTitleMessage = {
62 type: 'custom-title'
63 sessionId: UUID
64 customTitle: string
65}
66
67/**
68 * AI-generated session title. Distinct from CustomTitleMessage so that:
69 * - User renames (custom-title) always win over AI titles in read preference
70 * - reAppendSessionMetadata never re-appends AI titles (they're ephemeral/
71 * regeneratable; re-appending would clobber user renames on resume)
72 * - VS Code's onlyIfNoCustomTitle CAS check only matches user titles,
73 * allowing AI to overwrite its own previous AI title but not user titles
74 */
75export type AiTitleMessage = {
76 type: 'ai-title'
77 sessionId: UUID
78 aiTitle: string
79}
80
81export type LastPromptMessage = {
82 type: 'last-prompt'
83 sessionId: UUID
84 lastPrompt: string
85}
86
87/**
88 * Periodic fork-generated summary of what the agent is currently doing.
89 * Written every min(5 steps, 2min) by forking the main thread mid-turn so
90 * `claude ps` can show something more useful than the last user prompt
91 * (which is often "ok go" or "fix it").
92 */
93export type TaskSummaryMessage = {
94 type: 'task-summary'
95 sessionId: UUID
96 summary: string
97 timestamp: string
98}
99
100export type TagMessage = {
101 type: 'tag'
102 sessionId: UUID
103 tag: string
104}
105
106export type AgentNameMessage = {
107 type: 'agent-name'
108 sessionId: UUID
109 agentName: string
110}
111
112export type AgentColorMessage = {
113 type: 'agent-color'
114 sessionId: UUID
115 agentColor: string
116}
117
118export type AgentSettingMessage = {
119 type: 'agent-setting'
120 sessionId: UUID
121 agentSetting: string
122}
123
124/**
125 * PR link message stored in session transcript.
126 * Links a session to a GitHub pull request for tracking and navigation.
127 */
128export type PRLinkMessage = {
129 type: 'pr-link'
130 sessionId: UUID
131 prNumber: number
132 prUrl: string
133 prRepository: string // e.g., "owner/repo"
134 timestamp: string // ISO timestamp when linked
135}
136
137export type ModeEntry = {
138 type: 'mode'
139 sessionId: UUID
140 mode: 'coordinator' | 'normal'
141}
142
143/**
144 * Worktree session state persisted to the transcript for resume.
145 * Subset of WorktreeSession from utils/worktree.ts — excludes ephemeral
146 * fields (creationDurationMs, usedSparsePaths) that are only used for
147 * first-run analytics.
148 */
149export type PersistedWorktreeSession = {
150 originalCwd: string
151 worktreePath: string
152 worktreeName: string
153 worktreeBranch?: string
154 originalBranch?: string
155 originalHeadCommit?: string
156 sessionId: string
157 tmuxSessionName?: string
158 hookBased?: boolean
159}
160
161/**
162 * Records whether the session is currently inside a worktree created by
163 * EnterWorktree or --worktree. Last-wins: an enter writes the session,
164 * an exit writes null. On --resume, restored only if the worktreePath
165 * still exists on disk (the /exit dialog may have removed it).
166 */
167export type WorktreeStateEntry = {
168 type: 'worktree-state'
169 sessionId: UUID
170 worktreeSession: PersistedWorktreeSession | null
171}
172
173/**
174 * Records content blocks whose in-context representation was replaced with a
175 * smaller stub (the full content was persisted elsewhere). Replayed on resume
176 * for prompt cache stability. Written once per enforcement pass that replaces
177 * at least one block. When agentId is set, the record belongs to a subagent
178 * sidechain (AgentTool resume reads these); when absent, it's main-thread
179 * (/resume reads these).
180 */
181export type ContentReplacementEntry = {
182 type: 'content-replacement'
183 sessionId: UUID
184 agentId?: AgentId
185 replacements: ContentReplacementRecord[]
186}
187
188export type FileHistorySnapshotMessage = {
189 type: 'file-history-snapshot'
190 messageId: UUID
191 snapshot: FileHistorySnapshot
192 isSnapshotUpdate: boolean
193}
194
195/**
196 * Per-file attribution state tracking Claude's character contributions.
197 */
198export type FileAttributionState = {
199 contentHash: string // SHA-256 hash of file content
200 claudeContribution: number // Characters written by Claude
201 mtime: number // File modification time
202}
203
204/**
205 * Attribution snapshot message stored in session transcript.
206 * Tracks character-level contributions by Claude for commit attribution.
207 */
208export type AttributionSnapshotMessage = {
209 type: 'attribution-snapshot'
210 messageId: UUID
211 surface: string // Client surface (cli, ide, web, api)
212 fileStates: Record<string, FileAttributionState>
213 promptCount?: number // Total prompts in session
214 promptCountAtLastCommit?: number // Prompts at last commit
215 permissionPromptCount?: number // Total permission prompts shown
216 permissionPromptCountAtLastCommit?: number // Permission prompts at last commit
217 escapeCount?: number // Total ESC presses (cancelled permission prompts)
218 escapeCountAtLastCommit?: number // ESC presses at last commit
219}
220
221export type TranscriptMessage = SerializedMessage & {
222 parentUuid: UUID | null
223 logicalParentUuid?: UUID | null // Preserves logical parent when parentUuid is nullified for session breaks
224 isSidechain: boolean
225 gitBranch?: string
226 agentId?: string // Agent ID for sidechain transcripts to enable resuming agents
227 teamName?: string // Team name if this is a spawned agent session
228 agentName?: string // Agent's custom name (from /rename or swarm)
229 agentColor?: string // Agent's color (from /rename or swarm)
230 promptId?: string // Correlates with OTel prompt.id for user prompt messages
231}
232
233export type SpeculationAcceptMessage = {
234 type: 'speculation-accept'
235 timestamp: string
236 timeSavedMs: number
237}
238
239/**
240 * Persisted context-collapse commit. The archived messages themselves are
241 * NOT persisted — they're already in the transcript as ordinary user/
242 * assistant messages. We only persist enough to reconstruct the splice
243 * instruction (boundary uuids) and the summary placeholder (which is NOT
244 * in the transcript because it's never yielded to the REPL).
245 *
246 * On restore, the store reconstructs CommittedCollapse with archived=[];
247 * projectView lazily fills the archive the first time it finds the span.
248 *
249 * Discriminator is obfuscated to match the gate name. sessionStorage.ts
250 * isn't feature-gated (it's the generic transcript plumbing used by every
251 * entry type), so a descriptive string here would leak into external builds
252 * via the appendEntry dispatch / loadTranscriptFile parser even though
253 * nothing in an external build ever writes or reads this entry.
254 */
255export type ContextCollapseCommitEntry = {
256 type: 'marble-origami-commit'
257 sessionId: UUID
258 /** 16-digit collapse ID. Max across entries reseeds the ID counter. */
259 collapseId: string
260 /** The summary placeholder's uuid — registerSummary() needs it. */
261 summaryUuid: string
262 /** Full <collapsed id="...">text</collapsed> string for the placeholder. */
263 summaryContent: string
264 /** Plain summary text for ctx_inspect. */
265 summary: string
266 /** Span boundaries — projectView finds these in the resumed Message[]. */
267 firstArchivedUuid: string
268 lastArchivedUuid: string
269}
270
271/**
272 * Snapshot of the staged queue and spawn trigger state. Unlike commits
273 * (append-only, replay-all), snapshots are last-wins — only the most
274 * recent snapshot entry is applied on restore. Written after every
275 * ctx-agent spawn resolves (when staged contents may have changed).
276 *
277 * Staged boundaries are UUIDs (session-stable), not collapse IDs (which
278 * reset with the uuidToId bimap). Restoring a staged span issues fresh
279 * collapse IDs for those messages on the next decorate/display, but the
280 * span itself resolves correctly.
281 */
282export type ContextCollapseSnapshotEntry = {
283 type: 'marble-origami-snapshot'
284 sessionId: UUID
285 staged: Array<{
286 startUuid: string
287 endUuid: string
288 summary: string
289 risk: number
290 stagedAt: number
291 }>
292 /** Spawn trigger state — so the +interval clock picks up where it left off. */
293 armed: boolean
294 lastSpawnTokens: number
295}
296
297export type Entry =
298 | TranscriptMessage
299 | SummaryMessage
300 | CustomTitleMessage
301 | AiTitleMessage
302 | LastPromptMessage
303 | TaskSummaryMessage
304 | TagMessage
305 | AgentNameMessage
306 | AgentColorMessage
307 | AgentSettingMessage
308 | PRLinkMessage
309 | FileHistorySnapshotMessage
310 | AttributionSnapshotMessage
311 | QueueOperationMessage
312 | SpeculationAcceptMessage
313 | ModeEntry
314 | WorktreeStateEntry
315 | ContentReplacementEntry
316 | ContextCollapseCommitEntry
317 | ContextCollapseSnapshotEntry
318
319export function sortLogs(logs: LogOption[]): LogOption[] {
320 return logs.sort((a, b) => {
321 // Sort by modified date (newest first)
322 const modifiedDiff = b.modified.getTime() - a.modified.getTime()
323 if (modifiedDiff !== 0) {
324 return modifiedDiff
325 }
326
327 // If modified dates are equal, sort by created date (newest first)
328 return b.created.getTime() - a.created.getTime()
329 })
330}