Skip to content
3 changes: 2 additions & 1 deletion src/OpenClaw.Tray.WinUI/Chat/ChatConversationState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,8 @@ internal bool FailCommandCatalogFetch(int epoch)
{
lock (_gate)
{
return _presentation.IsCommandCatalogEpochCurrent(epoch)
return !_disposed &&
_presentation.IsCommandCatalogEpochCurrent(epoch)
? BuildSnapshotLocked(context)
: null;
}
Expand Down
61 changes: 46 additions & 15 deletions src/OpenClaw.Tray.WinUI/Chat/ChatStatePersistence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,25 +218,13 @@ internal void SaveSelectedState(OpenClawChatDataProvider.LastChatState state)

internal void DebounceSnapshot(ChatDataSnapshot snapshot)
{
var defaultThread = snapshot.DefaultThreadId is { } defaultId
? Array.Find(snapshot.Threads, thread => thread.Id == defaultId)
: snapshot.Threads.FirstOrDefault();
if (defaultThread is null && snapshot.AvailableModels.Length == 0)
return;

lock (_gate)
{
if (_disposed)
return;
var previous = _lastState;
var state = new OpenClawChatDataProvider.LastChatState
{
DefaultThreadId = snapshot.DefaultThreadId ?? previous?.DefaultThreadId,
ThreadTitle = defaultThread?.Title ?? previous?.ThreadTitle,
Model = defaultThread?.Model ?? previous?.Model,
ModelProvider = defaultThread?.ModelProvider ?? previous?.ModelProvider,
AvailableModels = snapshot.AvailableModels.ToArray(),
};
var state = CreateLastChatState(snapshot, _lastState);
if (state is null)
return;
_lastState = state;
var version = ++_lastStateSaveVersion;
_lastStateSaveTimer?.Dispose();
Expand All @@ -248,6 +236,29 @@ internal void DebounceSnapshot(ChatDataSnapshot snapshot)
}
}

internal void SaveSnapshot(ChatDataSnapshot snapshot)
{
Timer? timer;
OpenClawChatDataProvider.LastChatState? state;
lock (_gate)
{
if (_disposed)
return;
state = CreateLastChatState(snapshot, _lastState);
if (state is null)
return;
_lastState = state;
_lastStateSaveVersion++;
timer = _lastStateSaveTimer;
_lastStateSaveTimer = null;
// SaveLastStateIfCurrent also writes under this gate. Keeping the
// final write serialized ensures an in-flight debounce callback
// cannot overwrite the authoritative shutdown snapshot afterward.
SaveLastChatState(state, _lastStatePath);
}
timer?.Dispose();
}

public void Dispose()
{
Timer? timer;
Expand Down Expand Up @@ -368,6 +379,26 @@ private void SaveLastStateIfCurrent(
}
}

private static OpenClawChatDataProvider.LastChatState? CreateLastChatState(
ChatDataSnapshot snapshot,
OpenClawChatDataProvider.LastChatState? previous)
{
var defaultThread = snapshot.DefaultThreadId is { } defaultId
? Array.Find(snapshot.Threads, thread => thread.Id == defaultId)
: snapshot.Threads.FirstOrDefault();
if (defaultThread is null && snapshot.AvailableModels.Length == 0)
return null;

return new OpenClawChatDataProvider.LastChatState
{
DefaultThreadId = snapshot.DefaultThreadId ?? previous?.DefaultThreadId,
ThreadTitle = defaultThread?.Title ?? previous?.ThreadTitle,
Model = defaultThread?.Model ?? previous?.Model,
ModelProvider = defaultThread?.ModelProvider ?? previous?.ModelProvider,
AvailableModels = snapshot.AvailableModels.ToArray(),
};
}

private static void SaveLastChatState(
OpenClawChatDataProvider.LastChatState state,
string path)
Expand Down
Loading
Loading