mirror of
https://github.com/cline/cline.git
synced 2026-09-24 23:20:16 +08:00
* perf(sdk): stop listSessions hot loop from hanging the extension host
getStateToPostToWebview rebuilt the full task history on nearly every
streaming/session event, and each rebuild ran persistence-service.listSessions,
which synchronously read + Zod-parsed every session manifest. The 10s metadata
cache meant to absorb this was wiped on every per-turn updateTaskUsage, so each
state post paid the full synchronous scan, saturating the extension-host event
loop (observed as a tight listSessions/readFileUtf8 loop in CPU profiles).
- Debounce/coalesce postStateToWebview: trailing 50ms debounce plus a single
queued follow-up so bursts collapse into one rebuild; dispose() tears it down.
- Add an async, title-only manifest reader (readSessionManifestTitle) and use it
in listSessions to resolve titles concurrently off-thread, instead of a
synchronous readFileSync + full SessionManifestSchema (Zod) parse per row. The
existing sync manifest methods are left intact.
- On single-session updates, patch just the changed record in the merged-history
cache in place instead of invalidating it, so frequent per-turn usage updates
no longer force the next state post to re-enumerate and re-merge every session.
* refactor(sdk): strengthen session history cache patching
Replace patchMetadataHistoryCacheRecord (boolean-returning, metadata-only,
no re-sort) with updateCachedSessionRecord (void, updates prompt +
metadata + updatedAt, re-sorts via shared comparator).
- Void return eliminates the ignorable fallback contract.
- Mirrors all fields the persistence layer writes (prompt, metadata,
updatedAt) so cache and disk stay consistent.
- Re-sorts after patching so the updated record bubbles to the correct
position, using a shared compareSessionHistoryRecordsByRecencyDesc
comparator also used by listHistory.
- Derives updatedAt from the HistoryItem timestamp instead of constructing
a second clock value.
- Self-invalidates on cache miss so callers never manage the fallback.
Adds tests for in-place patching, re-sorting, per-turn usage hot path,
and cache-miss invalidation.
* fix(sdk): await in-flight state post during dispose
Greptile feedback: dispose() did not await a concurrently-running
runDebouncedStatePost, so an in-flight flushStateToWebview could access
torn-down resources after disposal.
Track the runDebouncedStatePost promise in statePostInFlightPromise.
In dispose(), after setting isDisposed and clearing the timer, await
the in-flight promise (swallowing errors) before tearing down downstream
resources. The !this.isDisposed guard in the loop prevents further
iterations after disposal.
* fix(sdk): address review feedback on state-post debounce and cache patch
Three issues from code review of the listSessions hot-loop fix:
1. dispose() could await the wrong promise. A second debounced timer
firing while a flush was already running overwrote
statePostInFlightPromise with a throwaway resolved promise from the
join path, so dispose() could return while the original flush was
still executing. Extract the debounce/coalesce state machine into
StatePostDebouncer, and only track the promise from the call that
actually starts a new flush loop.
2. postStateToWebview() swallowed flush errors, resolving every pending
caller even when flushStateToWebview() threw. Callers awaiting
postStateToWebview() now see the rejection, matching pre-debounce
behavior.
3. Cache patching derived the cached updatedAt from HistoryItem.ts,
but the persistence adapter always stamps updatedAt with the
wall-clock write time. Callers like toggleTaskFavorite() reuse an
old HistoryItem whose ts predates the write, which let the cached
ordering diverge from disk until the 10s TTL expired. Stamp the
cache patch with the write time instead.
Adds unit tests for StatePostDebouncer covering the dispose race and
error-propagation regressions, and a sdk-task-history test for the
stale-updatedAt cache-ordering regression.
* fix(sdk): don't patch cache when session update write didn't land
Beatrix's review feedback: updateSession() ignored the { updated:
boolean } result from host.update() and unconditionally patched the
metadata cache. When persistence returns updated: false (session
deleted/missing, or an optimistic-concurrency retry exhausted by a
racing writer), the webview could show a fake updated record until the
cache TTL expired.
Check the write result: only patch the cache when updated === true,
otherwise invalidate it so the next read re-enumerates from disk.
---------
Co-authored-by: Cline Agent <cline-agent@users.noreply.github.com>