@@ -68,15 +68,15 @@ def _entry_text_blocks(entry: dict[str, Any]) -> list[str]:
6868 return texts
6969
7070
71- def _fold_first_prompt (summary : SessionSummaryEntry , entry : dict [str , Any ]) -> None :
71+ def _fold_first_prompt (data : dict [ str , Any ] , entry : dict [str , Any ]) -> None :
7272 """Replicate ``_extract_first_prompt_from_head`` for a single parsed entry.
7373
74- Mutates ``summary `` in place: sets ``first_prompt`` +
75- ``_first_prompt_locked`` on a real match, or stashes a
76- ``_command_fallback`` for slash-command messages. Skips tool_result,
77- isMeta, isCompactSummary, and auto-generated patterns.
74+ Mutates ``data `` in place: sets ``first_prompt`` + ``first_prompt_locked``
75+ on a real match, or stashes a ``command_fallback`` for slash-command
76+ messages. Skips tool_result, isMeta, isCompactSummary, and auto-generated
77+ patterns.
7878 """
79- if summary .get ("_first_prompt_locked " ):
79+ if data .get ("first_prompt_locked " ):
8080 return
8181 if entry .get ("type" ) != "user" :
8282 return
@@ -97,15 +97,15 @@ def _fold_first_prompt(summary: SessionSummaryEntry, entry: dict[str, Any]) -> N
9797 continue
9898 cmd_match = _COMMAND_NAME_RE .search (result )
9999 if cmd_match :
100- if not summary .get ("_command_fallback " ):
101- summary [ "_command_fallback " ] = cmd_match .group (1 )
100+ if not data .get ("command_fallback " ):
101+ data [ "command_fallback " ] = cmd_match .group (1 )
102102 continue
103103 if _SKIP_FIRST_PROMPT_PATTERN .match (result ):
104104 continue
105105 if len (result ) > 200 :
106106 result = result [:200 ].rstrip () + "\u2026 "
107- summary ["first_prompt" ] = result
108- summary [ "_first_prompt_locked " ] = True
107+ data ["first_prompt" ] = result
108+ data [ "first_prompt_locked " ] = True
109109 return
110110
111111
@@ -121,15 +121,19 @@ def fold_session_summary(
121121 transcript. ``prev`` is the previous summary for the same key (or ``None``
122122 for the first append).
123123
124- Set-once fields (``is_sidechain``, ``created_at``, ``cwd``,
125- ``first_prompt``) freeze on first sight; last-wins fields
126- (``custom_title``, ``ai_title``, ``last_prompt``, ``summary_hint``,
127- ``git_branch``, ``tag``, ``mtime``) overwrite on every appearance.
124+ All derived state lives in the opaque ``data`` dict; stores persist it
125+ verbatim and do not interpret it. ``mtime`` stays top-level so stores
126+ can index on it.
128127 """
129128 if prev is not None :
130- summary = cast (SessionSummaryEntry , dict (prev ))
129+ summary : SessionSummaryEntry = {
130+ "session_id" : prev ["session_id" ],
131+ "mtime" : prev ["mtime" ],
132+ "data" : dict (prev ["data" ]),
133+ }
131134 else :
132- summary = {"session_id" : key ["session_id" ], "mtime" : 0 }
135+ summary = {"session_id" : key ["session_id" ], "mtime" : 0 , "data" : {}}
136+ data = summary ["data" ]
133137
134138 for raw in entries :
135139 # SessionStoreEntry is a permissive TypedDict; widen to a plain dict
@@ -140,30 +144,30 @@ def fold_session_summary(
140144 if ms is not None and ms > summary ["mtime" ]:
141145 summary ["mtime" ] = ms
142146
143- if "is_sidechain" not in summary :
144- summary ["is_sidechain" ] = entry .get ("isSidechain" ) is True
145- if "created_at" not in summary and ms is not None :
146- summary ["created_at" ] = ms
147+ if "is_sidechain" not in data :
148+ data ["is_sidechain" ] = entry .get ("isSidechain" ) is True
149+ if "created_at" not in data and ms is not None :
150+ data ["created_at" ] = ms
147151
148- if "cwd" not in summary :
152+ if "cwd" not in data :
149153 cwd = entry .get ("cwd" )
150154 if isinstance (cwd , str ) and cwd :
151- summary ["cwd" ] = cwd
155+ data ["cwd" ] = cwd
152156
153- _fold_first_prompt (summary , entry )
157+ _fold_first_prompt (data , entry )
154158
155159 for src , dst in _LAST_WINS_FIELDS .items ():
156160 val = entry .get (src )
157161 if isinstance (val , str ):
158- summary [dst ] = val # type: ignore[literal-required]
162+ data [dst ] = val
159163
160164 if entry .get ("type" ) == "tag" :
161165 tag_val = entry .get ("tag" )
162166 if isinstance (tag_val , str ) and tag_val :
163- summary ["tag" ] = tag_val
167+ data ["tag" ] = tag_val
164168 else :
165169 # Empty string or absent tag clears the tag.
166- summary .pop ("tag" , None )
170+ data .pop ("tag" , None )
167171
168172 return summary
169173
@@ -176,19 +180,20 @@ def summary_entry_to_sdk_info(
176180 Returns ``None`` for sidechain sessions or sessions with no extractable
177181 summary, matching ``_parse_session_info_from_lite``'s filtering.
178182 """
179- if entry .get ("is_sidechain" ):
183+ data = entry ["data" ]
184+ if data .get ("is_sidechain" ):
180185 return None
181186
182187 first_prompt = (
183- entry .get ("first_prompt" )
184- if entry .get ("_first_prompt_locked " )
185- else entry .get ("_command_fallback " )
188+ data .get ("first_prompt" )
189+ if data .get ("first_prompt_locked " )
190+ else data .get ("command_fallback " )
186191 ) or None
187- custom_title = entry .get ("custom_title" ) or entry .get ("ai_title" ) or None
192+ custom_title = data .get ("custom_title" ) or data .get ("ai_title" ) or None
188193 summary = (
189194 custom_title
190- or entry .get ("last_prompt" )
191- or entry .get ("summary_hint" )
195+ or data .get ("last_prompt" )
196+ or data .get ("summary_hint" )
192197 or first_prompt
193198 )
194199 if not summary :
@@ -198,11 +203,11 @@ def summary_entry_to_sdk_info(
198203 session_id = entry ["session_id" ],
199204 summary = summary ,
200205 last_modified = entry ["mtime" ],
201- file_size = entry .get ("file_size" ),
206+ file_size = data .get ("file_size" ),
202207 custom_title = custom_title ,
203208 first_prompt = first_prompt ,
204- git_branch = entry .get ("git_branch" ) or None ,
205- cwd = entry .get ("cwd" ) or project_path or None ,
206- tag = entry .get ("tag" ) or None ,
207- created_at = entry .get ("created_at" ),
209+ git_branch = data .get ("git_branch" ) or None ,
210+ cwd = data .get ("cwd" ) or project_path or None ,
211+ tag = data .get ("tag" ) or None ,
212+ created_at = data .get ("created_at" ),
208213 )
0 commit comments