fix: keep remote heartbeat UI side effect free
This commit is contained in:
@@ -85,16 +85,22 @@ public sealed class WindowsAgentBackend(AccountBindingStore bindings, ServiceOpt
|
||||
? string.Equals(binding.WechatId, current.WechatId, StringComparison.OrdinalIgnoreCase)
|
||||
: string.Equals(binding.Nickname, current.DisplayName, StringComparison.Ordinal);
|
||||
|
||||
public async Task<IReadOnlyList<AccountInfo>> AccountsAsync(CancellationToken cancellationToken)
|
||||
public Task<IReadOnlyList<AccountInfo>> AccountsAsync(CancellationToken cancellationToken) =>
|
||||
AccountsAsync(cancellationToken, refreshUiIdentity: true);
|
||||
|
||||
public async Task<IReadOnlyList<AccountInfo>> AccountsAsync(CancellationToken cancellationToken, bool refreshUiIdentity)
|
||||
{
|
||||
var targets = await ReadUiTargetsAsync(cancellationToken, true);
|
||||
var targets = await ReadUiTargetsAsync(cancellationToken, refreshUiIdentity);
|
||||
var accounts = await ReadDatabaseAccountsAsync(cancellationToken);
|
||||
await AutoBindMatchesAsync(accounts, targets, cancellationToken);
|
||||
if (refreshUiIdentity)
|
||||
await AutoBindMatchesAsync(accounts, targets, cancellationToken);
|
||||
var current = bindings.ReadAll();
|
||||
var singleBoundTarget = !refreshUiIdentity && current.Count == 1 && targets.Count == 1;
|
||||
var live = current.Where(binding => targets.Any(target =>
|
||||
target.ProcessId == binding.ProcessId && target.WindowHandle == binding.WindowHandle &&
|
||||
(string.IsNullOrWhiteSpace(target.WechatId) && string.IsNullOrWhiteSpace(target.Nickname) ||
|
||||
BindingMatches(binding, new WechatAccountSnapshot(target.Nickname ?? string.Empty, target.WechatId)))))
|
||||
(target.ProcessId == binding.ProcessId && target.WindowHandle == binding.WindowHandle &&
|
||||
(!refreshUiIdentity || string.IsNullOrWhiteSpace(target.WechatId) && string.IsNullOrWhiteSpace(target.Nickname) ||
|
||||
BindingMatches(binding, new WechatAccountSnapshot(target.Nickname ?? string.Empty, target.WechatId)))) ||
|
||||
singleBoundTarget))
|
||||
.Select(binding => binding.AccountId)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
return accounts.Select(account =>
|
||||
@@ -466,7 +472,9 @@ public sealed class WindowsAgentBackend(AccountBindingStore bindings, ServiceOpt
|
||||
return Task.FromResult<object>(new
|
||||
{
|
||||
serviceOnline = true,
|
||||
wechatAvailable = report.Errors.Count == 0,
|
||||
// Process/window presence is availability; a changed control tree is a degraded UI state,
|
||||
// not a stopped WeChat process.
|
||||
wechatAvailable = report.Processes.Count > 0 && report.WindowFound,
|
||||
sessionAvailable = report.UserInteractive && report.InputDesktopAvailable,
|
||||
sessionLocked = report.Errors.Contains(WxAgentErrorCode.SessionLocked),
|
||||
report.WindowFound,
|
||||
|
||||
@@ -14,6 +14,7 @@ public interface IAgentBackend
|
||||
Task<object> StatusAsync(CancellationToken cancellationToken);
|
||||
Task<object> DiagnoseAsync(CancellationToken cancellationToken) => StatusAsync(cancellationToken);
|
||||
Task<IReadOnlyList<AccountInfo>> AccountsAsync(CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<AccountInfo>>([]);
|
||||
Task<IReadOnlyList<AccountInfo>> AccountsAsync(CancellationToken cancellationToken, bool refreshUiIdentity) => AccountsAsync(cancellationToken);
|
||||
Task<IReadOnlyList<UiTargetInfo>> UiTargetsAsync(CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<UiTargetInfo>>([]);
|
||||
Task<AccountBinding> BindAccountAsync(string accountId, string targetId, CancellationToken cancellationToken) => throw new ServiceException("Unsupported", 501, "Account binding is not available.");
|
||||
Task UnbindAccountAsync(string accountId, CancellationToken cancellationToken) => throw new ServiceException("Unsupported", 501, "Account binding is not available.");
|
||||
|
||||
@@ -401,17 +401,31 @@ public sealed class RemoteAgentHostedService(
|
||||
var wechatRunning = GetBoolean(element, "wechatAvailable");
|
||||
var sessionAvailable = GetBoolean(element, "sessionAvailable");
|
||||
var sessionLocked = GetBoolean(element, "sessionLocked");
|
||||
var accounts = await backend.AccountsAsync(cancellationToken);
|
||||
// Heartbeats must not refresh UI identity. Binding already contains the verified identity;
|
||||
// the explicit accounts API remains the only path that may inspect the profile.
|
||||
var accounts = await backend.AccountsAsync(cancellationToken, refreshUiIdentity: false);
|
||||
var identities = accounts.Select(account => new RemoteAccountIdentity(
|
||||
account.AccountId,
|
||||
account.IsUiBindingKnown && account.Binding is not null && string.Equals(account.BindingStatus, "Bound", StringComparison.Ordinal))).ToArray();
|
||||
var activeAccountId = remote.ActiveAccountId;
|
||||
var boundAccounts = accounts
|
||||
.Where(account => account.IsUiBindingKnown && account.Binding is not null && string.Equals(account.BindingStatus, "Bound", StringComparison.Ordinal))
|
||||
.Select(account => account.AccountId)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
if ((string.IsNullOrWhiteSpace(activeAccountId) || !identities.Any(identity => identity.Verified && string.Equals(identity.AccountId, activeAccountId, StringComparison.Ordinal)))
|
||||
&& boundAccounts.Length == 1)
|
||||
{
|
||||
// A single verified binding is safe to use when the optional GUI value is empty or a display name.
|
||||
activeAccountId = boundAccounts[0];
|
||||
}
|
||||
var activeAccountVerified = false;
|
||||
if (remote.ActiveAccountId is { Length: > 0 })
|
||||
if (activeAccountId is { Length: > 0 })
|
||||
{
|
||||
try
|
||||
{
|
||||
accountContext.SwitchTo(remote.ActiveAccountId, identities);
|
||||
activeAccountVerified = accountContext.IsConfirmedFor(remote.ActiveAccountId);
|
||||
accountContext.SwitchTo(activeAccountId, identities);
|
||||
activeAccountVerified = accountContext.IsConfirmedFor(activeAccountId);
|
||||
}
|
||||
catch (WxAgentException)
|
||||
{
|
||||
@@ -427,7 +441,7 @@ public sealed class RemoteAgentHostedService(
|
||||
: !sessionAvailable ? RemoteNodeStatus.WechatNotLoggedIn
|
||||
: !activeAccountVerified ? RemoteNodeStatus.Degraded
|
||||
: RemoteNodeStatus.Online;
|
||||
return new BackendSnapshot(nodeStatus, wechatRunning, sessionAvailable, sessionLocked, remote.ActiveAccountId, 0, activeAccountVerified, identities);
|
||||
return new BackendSnapshot(nodeStatus, wechatRunning, sessionAvailable, sessionLocked, activeAccountId, 0, activeAccountVerified, identities);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user