@@ -127,6 +127,122 @@ function M.tail_info(file)
127127 return { state = state , last_activity = last_activity }
128128end
129129
130+ local PREVIEW_BLOCK_SIZE = 65536
131+ local PREVIEW_BUDGET = 1048576
132+
133+ local function read_bounded_tail (file , block_size , budget )
134+ local fd = io.open (file , " rb" )
135+ if not fd then
136+ return " "
137+ end
138+ local size = fd :seek (" end" )
139+ if size == 0 then
140+ fd :close ()
141+ return " "
142+ end
143+
144+ local blocks = {}
145+ local total_read = 0
146+ local pos = size
147+ while pos > 0 and total_read < budget do
148+ local read_size = math.min (block_size , pos , budget - total_read )
149+ pos = pos - read_size
150+ fd :seek (" set" , pos )
151+ local chunk = fd :read (read_size )
152+ if not chunk then
153+ break
154+ end
155+ table.insert (blocks , 1 , chunk )
156+ total_read = total_read + # chunk
157+ end
158+ fd :close ()
159+
160+ return table.concat (blocks )
161+ end
162+
163+ local function last_tool_use_name (content )
164+ if type (content ) ~= " table" then
165+ return nil
166+ end
167+ for i = # content , 1 , - 1 do
168+ local block = content [i ]
169+ if type (block ) == " table" and block .type == " tool_use" then
170+ return block .name
171+ end
172+ end
173+ return nil
174+ end
175+
176+ local function assistant_text (content )
177+ if type (content ) == " string" then
178+ return content :match (" ^%s*(.-)%s*$" )
179+ end
180+ if type (content ) ~= " table" then
181+ return nil
182+ end
183+ local parts = {}
184+ for _ , block in ipairs (content ) do
185+ if type (block ) == " table" and block .type == " text" and type (block .text ) == " string" then
186+ parts [# parts + 1 ] = block .text
187+ end
188+ end
189+ if # parts == 0 then
190+ return nil
191+ end
192+ return table.concat (parts , " \n " ):match (" ^%s*(.-)%s*$" )
193+ end
194+
195+ function M .preview (file )
196+ local info = M .tail_info (file )
197+ if not info then
198+ return nil
199+ end
200+
201+ local content = read_bounded_tail (file , PREVIEW_BLOCK_SIZE , PREVIEW_BUDGET )
202+ local lines = vim .split (content , " \n " , { plain = true })
203+
204+ local last_message = nil
205+ local last_message_type = nil
206+ local found_assistant = false
207+ local last_assistant_content = nil
208+ for i = # lines , 1 , - 1 do
209+ local line = lines [i ]
210+ if line ~= " " then
211+ local ok , decoded = pcall (vim .json .decode , line )
212+ if ok and type (decoded ) == " table" and type (decoded .message ) == " table" then
213+ if not last_message and (decoded .type == " assistant" or decoded .type == " user" ) then
214+ last_message = decoded .message
215+ last_message_type = decoded .type
216+ end
217+ if not found_assistant and decoded .type == " assistant" then
218+ found_assistant = true
219+ last_assistant_content = decoded .message .content
220+ end
221+ end
222+ end
223+ if last_message and found_assistant then
224+ break
225+ end
226+ end
227+
228+ local tool = nil
229+ if info .state == " working" and last_message_type == " assistant" and last_message .stop_reason == " tool_use" then
230+ tool = last_tool_use_name (last_message .content )
231+ end
232+
233+ local text = nil
234+ if found_assistant then
235+ text = assistant_text (last_assistant_content )
236+ end
237+
238+ return {
239+ state = info .state ,
240+ last_activity = info .last_activity ,
241+ tool = tool ,
242+ text = text ,
243+ }
244+ end
245+
130246function M .session_file (cwd , sessions_dir , id )
131247 local path = sessions_dir .. " /" .. M .slug (cwd ) .. " /" .. id .. " .jsonl"
132248 if vim .fn .filereadable (path ) == 1 then
0 commit comments