mirror of
https://github.com/block/buzz.git
synced 2026-11-01 15:01:12 +00:00
Preserve agent activity across thread scopes
Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -155,17 +155,28 @@ final composerActivityStateProvider = Provider.autoDispose
|
||||
/// Agent pubkeys working in a channel, for the members badge and sheet.
|
||||
final workingBotPubkeysProvider = Provider.autoDispose
|
||||
.family<Set<String>, String>((ref, channelId) {
|
||||
final activity = ref.watch(
|
||||
composerActivityStateProvider((
|
||||
channelId: channelId,
|
||||
threadHeadId: null,
|
||||
)),
|
||||
);
|
||||
return Set.unmodifiable(
|
||||
activity.agents
|
||||
.where((agent) => agent.isWorking)
|
||||
.map((agent) => agent.pubkey),
|
||||
);
|
||||
final threadScopes = <String?>{
|
||||
null,
|
||||
for (final entry in ref.watch(channelTypingProvider(channelId)))
|
||||
entry.threadHeadId,
|
||||
for (final turn in ref.watch(composerAgentTurnStatesProvider))
|
||||
if (turn.channelId == channelId) turn.threadHeadId,
|
||||
};
|
||||
final working = <String>{};
|
||||
for (final threadHeadId in threadScopes) {
|
||||
final activity = ref.watch(
|
||||
composerActivityStateProvider((
|
||||
channelId: channelId,
|
||||
threadHeadId: threadHeadId,
|
||||
)),
|
||||
);
|
||||
working.addAll(
|
||||
activity.agents
|
||||
.where((agent) => agent.isWorking)
|
||||
.map((agent) => agent.pubkey),
|
||||
);
|
||||
}
|
||||
return Set.unmodifiable(working);
|
||||
});
|
||||
|
||||
int _compareComposerTurnRecency(AgentTurnState a, AgentTurnState b) {
|
||||
|
||||
@@ -724,7 +724,7 @@ class ThreadDetailPage extends HookConsumerWidget {
|
||||
if (!isMember || isArchived)
|
||||
_ThreadTypingIndicator(
|
||||
channelId: channelId,
|
||||
threadHeadId: threadHead.id,
|
||||
threadHeadId: effectiveRootId,
|
||||
animated: false,
|
||||
overlayTopBoundary: frostedAppBarHeight(context),
|
||||
),
|
||||
@@ -751,7 +751,7 @@ class ThreadDetailPage extends HookConsumerWidget {
|
||||
restoreComposerFocus,
|
||||
) => _ThreadTypingIndicator(
|
||||
channelId: channelId,
|
||||
threadHeadId: threadHead.id,
|
||||
threadHeadId: effectiveRootId,
|
||||
horizontalInset: 0,
|
||||
overlayTopBoundary: frostedAppBarHeight(context),
|
||||
compactWidthFactor: 0.85,
|
||||
|
||||
@@ -259,6 +259,52 @@ void main() {
|
||||
expect(container.read(workingBotPubkeysProvider(_channelId)), isEmpty);
|
||||
});
|
||||
|
||||
test('channel working set includes agents from every thread scope', () {
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
currentPubkeyProvider.overrideWith((ref) => 'owner'),
|
||||
channelMembersProvider(
|
||||
_channelId,
|
||||
).overrideWith((ref) async => const <ChannelMember>[]),
|
||||
channelTypingProvider(_channelId).overrideWith(
|
||||
() => _FakeTypingNotifier(const [
|
||||
TypingEntry(
|
||||
pubkey: 'agent-a',
|
||||
threadHeadId: 'thread-1',
|
||||
expiresAtMs: 9999999999999,
|
||||
),
|
||||
TypingEntry(
|
||||
pubkey: 'human',
|
||||
threadHeadId: 'thread-3',
|
||||
expiresAtMs: 9999999999999,
|
||||
),
|
||||
]),
|
||||
),
|
||||
agentMentionPubkeysProvider(
|
||||
_channelId,
|
||||
).overrideWith((ref) => const {'agent-a', 'agent-b'}),
|
||||
agentOwnersProvider.overrideWithValue(
|
||||
const AsyncData({'agent-a': 'owner', 'agent-b': 'owner'}),
|
||||
),
|
||||
userCacheProvider.overrideWith(_FakeUserCacheNotifier.new),
|
||||
observerRelayProvider.overrideWith(
|
||||
() => _FakeObserverRelayNotifier({
|
||||
'agent-b': [_observerFrame('agent-b')],
|
||||
}),
|
||||
),
|
||||
composerAgentTurnStatesProvider.overrideWithValue([
|
||||
_turn('agent-b', threadHeadId: 'thread-2'),
|
||||
]),
|
||||
],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
expect(container.read(workingBotPubkeysProvider(_channelId)), {
|
||||
'agent-a',
|
||||
'agent-b',
|
||||
});
|
||||
});
|
||||
|
||||
test('prefers live work over retained terminal history', () {
|
||||
final receiptAt = DateTime.utc(2026, 8, 16, 12, 0, 5);
|
||||
final container = ProviderContainer(
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:buzz/features/channels/channel.dart';
|
||||
import 'package:buzz/features/channels/agent_activity/active_agent_turns.dart';
|
||||
import 'package:buzz/features/channels/agent_activity/composer_agent_activity_indicator.dart';
|
||||
import 'package:buzz/features/channels/agent_activity/observer_models.dart';
|
||||
import 'package:buzz/features/channels/agent_activity/observer_subscription.dart';
|
||||
import 'package:buzz/features/channels/agent_activity/working_bots_provider.dart';
|
||||
@@ -4561,6 +4562,11 @@ void main() {
|
||||
expect(threadPage.threadHead.id, 'parent');
|
||||
expect(threadPage.initialMessageId, 'target');
|
||||
|
||||
final activityIndicator = tester.widget<ComposerAgentActivityIndicator>(
|
||||
find.byType(ComposerAgentActivityIndicator),
|
||||
);
|
||||
expect(activityIndicator.threadHeadId, 'root');
|
||||
|
||||
final highlighted = tester.widget<DecoratedBox>(
|
||||
find.byKey(const ValueKey('thread-message-target')),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user