@agent-relay/dashboard 2.0.87 → 2.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/404.html +1 -1
- package/out/_next/static/chunks/270-8c0b8109123a0c5f.js +73 -0
- package/out/_next/static/chunks/{1028-85d028e818d3ef30.js → 9626-10e71fc51b892784.js} +1 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/{page-f33c6214e21ccfdd.js → page-4f01a33b51f23cea.js} +1 -1
- package/out/_next/static/chunks/app/{page-2e38080856c3c293.js → page-2644ed4067d978e0.js} +1 -1
- package/out/about.html +1 -1
- package/out/about.txt +1 -1
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +1 -1
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +1 -1
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +1 -1
- package/out/blog.html +1 -1
- package/out/blog.txt +1 -1
- package/out/careers.html +1 -1
- package/out/careers.txt +1 -1
- package/out/changelog.html +1 -1
- package/out/changelog.txt +1 -1
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +1 -1
- package/out/complete-profile.html +1 -1
- package/out/complete-profile.txt +1 -1
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +1 -1
- package/out/contact.html +1 -1
- package/out/contact.txt +1 -1
- package/out/dev/cli-tools.html +1 -1
- package/out/dev/cli-tools.txt +1 -1
- package/out/dev/log-viewer.html +1 -1
- package/out/dev/log-viewer.txt +1 -1
- package/out/docs.html +1 -1
- package/out/docs.txt +1 -1
- package/out/history.html +1 -1
- package/out/history.txt +1 -1
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +1 -1
- package/out/login.txt +1 -1
- package/out/metrics.html +1 -1
- package/out/metrics.txt +1 -1
- package/out/pricing.html +1 -1
- package/out/pricing.txt +1 -1
- package/out/privacy.html +1 -1
- package/out/privacy.txt +1 -1
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +1 -1
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +1 -1
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +1 -1
- package/out/providers.html +1 -1
- package/out/providers.txt +1 -1
- package/out/security.html +1 -1
- package/out/security.txt +1 -1
- package/out/signup.html +1 -1
- package/out/signup.txt +1 -1
- package/out/terms.html +1 -1
- package/out/terms.txt +1 -1
- package/package.json +4 -3
- package/src/components/ChannelChat.tsx +1 -1
- package/src/components/ChannelSidebar.tsx +2 -2
- package/src/components/hooks/useAllDMs.ts +88 -0
- package/src/components/hooks/useDirectMessage.ts +12 -0
- package/src/lib/relaycastMessageAdapters.test.ts +30 -1
- package/src/lib/relaycastMessageAdapters.ts +28 -7
- package/src/providers/ChannelProvider.tsx +15 -4
- package/src/providers/MessageProvider.tsx +20 -21
- package/src/providers/RelayConfigProvider.tsx +0 -2
- package/out/_next/static/chunks/3238-24c1e4b1cefe3c71.js +0 -73
- /package/out/_next/static/{Z91dsVnsvqDCvvYfCXRXH → SiFRCTvfNgk02ZX5qxVBQ}/_buildManifest.js +0 -0
- /package/out/_next/static/{Z91dsVnsvqDCvvYfCXRXH → SiFRCTvfNgk02ZX5qxVBQ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAllDMs Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetches ALL DM conversations in the workspace using the workspace-level API
|
|
5
|
+
* (`allDmConversations`). Unlike the SDK's built-in `useDMs` which only returns
|
|
6
|
+
* conversations the dashboard agent participates in, this hook returns agent-to-agent
|
|
7
|
+
* DMs as well, making them visible in the DM sidebar.
|
|
8
|
+
*
|
|
9
|
+
* Listens for `dm.received` and `group_dm.received` WebSocket events to
|
|
10
|
+
* automatically refetch when new DMs arrive.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
|
+
import { useRelay, useEvent } from '@relaycast/react';
|
|
15
|
+
import { useRelayConfigStatus } from '../../providers/RelayConfigProvider';
|
|
16
|
+
|
|
17
|
+
export interface AllDmConversation {
|
|
18
|
+
id: string;
|
|
19
|
+
channelId?: string;
|
|
20
|
+
type: string;
|
|
21
|
+
participants: string[];
|
|
22
|
+
lastMessage: {
|
|
23
|
+
text: string;
|
|
24
|
+
agentName: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
} | null;
|
|
27
|
+
messageCount: number;
|
|
28
|
+
unreadCount?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface UseAllDMsResult {
|
|
32
|
+
conversations: AllDmConversation[];
|
|
33
|
+
loading: boolean;
|
|
34
|
+
error: Error | null;
|
|
35
|
+
refetch: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useAllDMs(): UseAllDMsResult {
|
|
39
|
+
const { configured } = useRelayConfigStatus();
|
|
40
|
+
const [conversations, setConversations] = useState<AllDmConversation[]>([]);
|
|
41
|
+
const [loading, setLoading] = useState(true);
|
|
42
|
+
const [error, setError] = useState<Error | null>(null);
|
|
43
|
+
const fetchingRef = useRef(false);
|
|
44
|
+
|
|
45
|
+
// useRelay() throws if not inside RelayProvider, but we always are.
|
|
46
|
+
// When relay is not configured, the provider uses dummy credentials and
|
|
47
|
+
// allDmConversations will fail — we guard with `configured`.
|
|
48
|
+
let relay: ReturnType<typeof useRelay> | null = null;
|
|
49
|
+
try {
|
|
50
|
+
relay = useRelay();
|
|
51
|
+
} catch {
|
|
52
|
+
// Not inside RelayProvider — relay stays null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fetchConversations = useCallback(async () => {
|
|
56
|
+
if (!configured || !relay || fetchingRef.current) return;
|
|
57
|
+
fetchingRef.current = true;
|
|
58
|
+
try {
|
|
59
|
+
const data = await relay.allDmConversations();
|
|
60
|
+
setConversations(Array.isArray(data) ? data as AllDmConversation[] : []);
|
|
61
|
+
setError(null);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
64
|
+
} finally {
|
|
65
|
+
setLoading(false);
|
|
66
|
+
fetchingRef.current = false;
|
|
67
|
+
}
|
|
68
|
+
}, [configured, relay]);
|
|
69
|
+
|
|
70
|
+
// Initial fetch
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!configured) {
|
|
73
|
+
setLoading(false);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
void fetchConversations();
|
|
77
|
+
}, [configured, fetchConversations]);
|
|
78
|
+
|
|
79
|
+
// Refetch when DM events arrive via WebSocket
|
|
80
|
+
useEvent('dm.received', () => {
|
|
81
|
+
void fetchConversations();
|
|
82
|
+
});
|
|
83
|
+
useEvent('group_dm.received', () => {
|
|
84
|
+
void fetchConversations();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return { conversations, loading, error, refetch: fetchConversations };
|
|
88
|
+
}
|
|
@@ -41,10 +41,22 @@ export function useDirectMessage({
|
|
|
41
41
|
for (const msg of messages) {
|
|
42
42
|
const { from, to } = msg;
|
|
43
43
|
if (!from || !to) continue;
|
|
44
|
+
// Messages involving the selected human
|
|
44
45
|
if (from === humanName && agentNameSet.has(to)) derived.add(to);
|
|
45
46
|
if (to === humanName && agentNameSet.has(from)) derived.add(from);
|
|
46
47
|
if (selectedDmAgents.includes(from) && agentNameSet.has(to)) derived.add(to);
|
|
47
48
|
if (selectedDmAgents.includes(to) && agentNameSet.has(from)) derived.add(from);
|
|
49
|
+
// Include agents from DM messages (non-channel messages) so agent-to-agent
|
|
50
|
+
// DMs are visible in the DM view — but only if the message involves the
|
|
51
|
+
// current human or an already-selected DM agent to avoid cross-conversation leakage.
|
|
52
|
+
if (!to.startsWith('#')) {
|
|
53
|
+
const involvesHuman = (from === humanName || to === humanName);
|
|
54
|
+
const involvesSelected = selectedDmAgents.includes(from) || selectedDmAgents.includes(to);
|
|
55
|
+
if (involvesHuman || involvesSelected) {
|
|
56
|
+
if (agentNameSet.has(from)) derived.add(from);
|
|
57
|
+
if (agentNameSet.has(to)) derived.add(to);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
const participants = new Set<string>([...selectedDmAgents, ...derived]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { Message } from '../types';
|
|
3
|
-
import { normalizeRelayDmMessageTargets } from './relaycastMessageAdapters.js';
|
|
3
|
+
import { getRelayDmParticipantName, normalizeRelayDmMessageTargets } from './relaycastMessageAdapters.js';
|
|
4
4
|
|
|
5
5
|
function setRelayUsername(value?: string): void {
|
|
6
6
|
const storage = (globalThis as { localStorage?: Storage }).localStorage;
|
|
@@ -93,6 +93,35 @@ describe('normalizeRelayDmMessageTargets', () => {
|
|
|
93
93
|
expect(normalized[0]?.to).toBe('Natty');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
it('maps dm_* targets to object participants using agent_name', () => {
|
|
97
|
+
const messages: Message[] = [
|
|
98
|
+
{
|
|
99
|
+
id: 'msg-2a',
|
|
100
|
+
from: 'Natty',
|
|
101
|
+
to: 'dm_7b62c72644b9316e7e10a992',
|
|
102
|
+
content: 'hello',
|
|
103
|
+
timestamp: '2026-02-24T12:00:06.000Z',
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const normalized = normalizeRelayDmMessageTargets(messages, [
|
|
108
|
+
{
|
|
109
|
+
id: 'dm_7b62c72644b9316e7e10a992',
|
|
110
|
+
participants: [{ agent_name: 'Natty' }, { agent_name: 'test-broker-new' }],
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
expect(normalized[0]?.to).toBe('test-broker-new');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('normalizes participant names using getRelayDmParticipantName', () => {
|
|
118
|
+
expect(getRelayDmParticipantName({ agent_name: 'Lead', name: 'ignored' })).toBe('Lead');
|
|
119
|
+
expect(getRelayDmParticipantName({ agentName: 'Codex-Worker' })).toBe('Codex-Worker');
|
|
120
|
+
expect(getRelayDmParticipantName('Test-Broker')).toBe('Test-Broker');
|
|
121
|
+
expect(getRelayDmParticipantName({ username: 'human-user' })).toBe('human-user');
|
|
122
|
+
expect(getRelayDmParticipantName(123)).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
|
|
96
125
|
it('leaves non-dm and unknown dm targets unchanged', () => {
|
|
97
126
|
const messages: Message[] = [
|
|
98
127
|
{
|
|
@@ -12,6 +12,30 @@ type RelayDmConversationLike = {
|
|
|
12
12
|
participants: unknown[];
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
export function getRelayDmParticipantName(participant: unknown): string | null {
|
|
16
|
+
if (typeof participant === 'string') {
|
|
17
|
+
return normalizeRelayIdentity(participant);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (participant === null || typeof participant !== 'object') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const record = participant as Record<string, unknown>;
|
|
25
|
+
const rawName = (
|
|
26
|
+
record.agent_name
|
|
27
|
+
?? record.agentName
|
|
28
|
+
?? record.name
|
|
29
|
+
?? record.username
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (typeof rawName !== 'string') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return normalizeRelayIdentity(rawName);
|
|
37
|
+
}
|
|
38
|
+
|
|
15
39
|
function normalizeRelayIdentity(value: string): string {
|
|
16
40
|
const trimmed = value.trim();
|
|
17
41
|
if (!trimmed) return '';
|
|
@@ -39,8 +63,7 @@ function resolveDmRecipient(
|
|
|
39
63
|
const senderKey = normalizeRelayIdentity(sender).toLowerCase();
|
|
40
64
|
|
|
41
65
|
for (const participant of participants) {
|
|
42
|
-
|
|
43
|
-
const normalized = normalizeRelayIdentity(participant);
|
|
66
|
+
const normalized = getRelayDmParticipantName(participant);
|
|
44
67
|
if (!normalized) continue;
|
|
45
68
|
if (normalized.toLowerCase() !== senderKey) {
|
|
46
69
|
return normalized;
|
|
@@ -48,11 +71,9 @@ function resolveDmRecipient(
|
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
for (const participant of participants) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return normalized;
|
|
55
|
-
}
|
|
74
|
+
const normalized = getRelayDmParticipantName(participant);
|
|
75
|
+
if (!normalized) continue;
|
|
76
|
+
return normalized;
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
return null;
|
|
@@ -118,9 +118,20 @@ export function ChannelProvider({ children }: ChannelProviderProps) {
|
|
|
118
118
|
|
|
119
119
|
// Relay channel state
|
|
120
120
|
const relayChannelsState = useRelayChannels();
|
|
121
|
+
const relayChannelsLoading = relayChannelsState.loading;
|
|
122
|
+
const relayChannelsRaw = relayChannelsState.channels;
|
|
123
|
+
|
|
124
|
+
// Stabilize the mapped channels array — only recompute when the serialized
|
|
125
|
+
// channel list actually changes (avoids infinite re-render loops from new
|
|
126
|
+
// array references returned by the relay hook on every render).
|
|
127
|
+
const relayChannelsKey = useMemo(
|
|
128
|
+
() => JSON.stringify(relayChannelsRaw.map(c => c.name + ':' + (c.topic ?? '') + ':' + (c.isArchived ?? false) + ':' + (c.memberCount ?? 0))),
|
|
129
|
+
[relayChannelsRaw],
|
|
130
|
+
);
|
|
121
131
|
const relayMappedChannels = useMemo(
|
|
122
|
-
() =>
|
|
123
|
-
|
|
132
|
+
() => relayChannelsRaw.map(mapRelayChannelToDashboard),
|
|
133
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
|
+
[relayChannelsKey],
|
|
124
135
|
);
|
|
125
136
|
|
|
126
137
|
// Channel list state
|
|
@@ -225,7 +236,7 @@ export function ChannelProvider({ children }: ChannelProviderProps) {
|
|
|
225
236
|
const activeChannels = relayMappedChannels.filter((channel) => channel.status !== 'archived');
|
|
226
237
|
const archivedChannels = relayMappedChannels.filter((channel) => channel.status === 'archived');
|
|
227
238
|
setChannelListsFromResponse({ channels: activeChannels, archivedChannels });
|
|
228
|
-
setIsChannelsLoading(
|
|
239
|
+
setIsChannelsLoading(relayChannelsLoading);
|
|
229
240
|
return;
|
|
230
241
|
}
|
|
231
242
|
|
|
@@ -253,7 +264,7 @@ export function ChannelProvider({ children }: ChannelProviderProps) {
|
|
|
253
264
|
fetchChannels();
|
|
254
265
|
}, [
|
|
255
266
|
relayConfigured,
|
|
256
|
-
|
|
267
|
+
relayChannelsLoading,
|
|
257
268
|
relayMappedChannels,
|
|
258
269
|
effectiveActiveWorkspaceId,
|
|
259
270
|
isWorkspaceFeaturesEnabled,
|
|
@@ -12,19 +12,18 @@
|
|
|
12
12
|
import React, { createContext, useContext, useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
13
13
|
import type { Message } from '../types';
|
|
14
14
|
import type { HumanUser } from '../components/MentionAutocomplete';
|
|
15
|
-
import {
|
|
16
|
-
useDMs as useRelayDMs,
|
|
17
|
-
} from '@relaycast/react';
|
|
18
15
|
import { useMessages as useMessagesHook } from '../components/hooks/useMessages';
|
|
19
16
|
import { useThread } from '../components/hooks/useThread';
|
|
20
17
|
import { usePresence, type UserPresence } from '../components/hooks/usePresence';
|
|
21
18
|
import { useDirectMessage } from '../components/hooks/useDirectMessage';
|
|
19
|
+
import { useAllDMs } from '../components/hooks/useAllDMs';
|
|
22
20
|
import { useCloudWorkspace } from './CloudWorkspaceProvider';
|
|
23
21
|
import { useAgentContext } from './AgentProvider';
|
|
24
22
|
import { useRelayConfigStatus } from './RelayConfigProvider';
|
|
25
23
|
import { isDashboardVariant } from '../lib/identity';
|
|
26
24
|
import {
|
|
27
25
|
normalizeRelayDmMessageTargets,
|
|
26
|
+
getRelayDmParticipantName,
|
|
28
27
|
} from '../lib/relaycastMessageAdapters';
|
|
29
28
|
import { playNotificationSound } from './SettingsProvider';
|
|
30
29
|
import { useSettings } from './SettingsProvider';
|
|
@@ -377,14 +376,14 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
377
376
|
// Relay DMs and message normalization
|
|
378
377
|
// ---------------------------------------------------------------------------
|
|
379
378
|
|
|
380
|
-
const
|
|
379
|
+
const allDMsState = useAllDMs();
|
|
381
380
|
const normalizedRelayMessages = useMemo(() => {
|
|
382
381
|
const sourceMessages = data?.messages ?? [];
|
|
383
|
-
if (!relayConfigured ||
|
|
382
|
+
if (!relayConfigured || allDMsState.conversations.length === 0) {
|
|
384
383
|
return sourceMessages;
|
|
385
384
|
}
|
|
386
|
-
return normalizeRelayDmMessageTargets(sourceMessages,
|
|
387
|
-
}, [data?.messages, relayConfigured,
|
|
385
|
+
return normalizeRelayDmMessageTargets(sourceMessages, allDMsState.conversations);
|
|
386
|
+
}, [data?.messages, relayConfigured, allDMsState.conversations]);
|
|
388
387
|
|
|
389
388
|
// ---------------------------------------------------------------------------
|
|
390
389
|
// Core message hook
|
|
@@ -441,7 +440,7 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
441
440
|
const { visibleMessages: dedupedVisibleMessages, participantAgents: dmParticipantAgents } = useDirectMessage({
|
|
442
441
|
currentHuman,
|
|
443
442
|
currentUserName: currentUser?.displayName ?? null,
|
|
444
|
-
messages,
|
|
443
|
+
messages: currentHuman ? normalizedRelayMessages : messages,
|
|
445
444
|
agents,
|
|
446
445
|
selectedDmAgents,
|
|
447
446
|
removedDmAgents,
|
|
@@ -466,11 +465,11 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
466
465
|
});
|
|
467
466
|
}
|
|
468
467
|
|
|
469
|
-
if (relayConfigured &&
|
|
468
|
+
if (relayConfigured && allDMsState.conversations.length > 0) {
|
|
470
469
|
const currentUserName = currentUser?.displayName.toLowerCase();
|
|
471
|
-
for (const conversation of
|
|
470
|
+
for (const conversation of allDMsState.conversations) {
|
|
472
471
|
for (const participant of conversation.participants) {
|
|
473
|
-
const name =
|
|
472
|
+
const name = getRelayDmParticipantName(participant);
|
|
474
473
|
if (!name) continue;
|
|
475
474
|
const lowered = name.toLowerCase();
|
|
476
475
|
if (currentUserName && lowered === currentUserName) continue;
|
|
@@ -490,7 +489,7 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
490
489
|
}
|
|
491
490
|
|
|
492
491
|
return Array.from(seenUsers.values());
|
|
493
|
-
}, [normalizedRelayMessages, agents, currentUser,
|
|
492
|
+
}, [normalizedRelayMessages, agents, currentUser, allDMsState.conversations, relayConfigured, relayAgentName]);
|
|
494
493
|
|
|
495
494
|
// ---------------------------------------------------------------------------
|
|
496
495
|
// Human unread counts
|
|
@@ -499,21 +498,21 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
499
498
|
const humanUnreadCounts = useMemo(() => {
|
|
500
499
|
if (!currentUser) return {};
|
|
501
500
|
|
|
502
|
-
if (relayConfigured &&
|
|
501
|
+
if (relayConfigured && allDMsState.conversations.length > 0) {
|
|
503
502
|
const counts: Record<string, number> = {};
|
|
504
503
|
const currentUserName = currentUser.displayName.toLowerCase();
|
|
505
504
|
const agentNames = new Set(agents.filter((a) => !a.isHuman).map((a) => a.name.toLowerCase()));
|
|
506
505
|
|
|
507
|
-
for (const conversation of
|
|
506
|
+
for (const conversation of allDMsState.conversations) {
|
|
508
507
|
if (!conversation.unreadCount) continue;
|
|
509
508
|
|
|
510
509
|
const match = conversation.participants.find((p) => {
|
|
511
|
-
const name =
|
|
510
|
+
const name = getRelayDmParticipantName(p);
|
|
512
511
|
if (!name) return false;
|
|
513
512
|
const lowered = name.toLowerCase();
|
|
514
513
|
return lowered !== currentUserName && !agentNames.has(lowered);
|
|
515
514
|
});
|
|
516
|
-
const participantName = match ? (
|
|
515
|
+
const participantName = match ? getRelayDmParticipantName(match) : null;
|
|
517
516
|
|
|
518
517
|
if (participantName) {
|
|
519
518
|
counts[participantName] = (counts[participantName] || 0) + conversation.unreadCount;
|
|
@@ -545,7 +544,7 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
545
544
|
}
|
|
546
545
|
|
|
547
546
|
return counts;
|
|
548
|
-
}, [combinedAgents, currentUser, normalizedRelayMessages, dmSeenAt,
|
|
547
|
+
}, [combinedAgents, currentUser, normalizedRelayMessages, dmSeenAt, allDMsState.conversations, agents, relayConfigured]);
|
|
549
548
|
|
|
550
549
|
const markDmSeen = useCallback((username: string) => {
|
|
551
550
|
setDmSeenAt((prev) => {
|
|
@@ -838,16 +837,16 @@ function MessageProviderInnerWithSend({ children, data, rawData, enableReactions
|
|
|
838
837
|
// Since SendProvider only needs them for local (non-cloud) channel message rendering,
|
|
839
838
|
// we derive them here at this level too.
|
|
840
839
|
const { configured: relayConfigured } = useRelayConfigStatus();
|
|
841
|
-
const
|
|
840
|
+
const allDMsState = useAllDMs();
|
|
842
841
|
const { currentUser } = useCloudWorkspace();
|
|
843
842
|
|
|
844
843
|
const normalizedRelayMessages = useMemo(() => {
|
|
845
844
|
const sourceMessages = data?.messages ?? [];
|
|
846
|
-
if (!relayConfigured ||
|
|
845
|
+
if (!relayConfigured || allDMsState.conversations.length === 0) {
|
|
847
846
|
return sourceMessages;
|
|
848
847
|
}
|
|
849
|
-
return normalizeRelayDmMessageTargets(sourceMessages,
|
|
850
|
-
}, [data?.messages, relayConfigured,
|
|
848
|
+
return normalizeRelayDmMessageTargets(sourceMessages, allDMsState.conversations);
|
|
849
|
+
}, [data?.messages, relayConfigured, allDMsState.conversations]);
|
|
851
850
|
|
|
852
851
|
const [localUsername] = useState<string | null>(
|
|
853
852
|
typeof window !== 'undefined' ? localStorage.getItem('relay_username') : null
|
|
@@ -84,8 +84,6 @@ export function RelayConfigProvider({ children }: RelayConfigProviderProps) {
|
|
|
84
84
|
}, [configured, config]);
|
|
85
85
|
|
|
86
86
|
// Channels to auto-subscribe on WebSocket connect/reconnect.
|
|
87
|
-
// This ensures the dashboard receives real-time messages even after
|
|
88
|
-
// WebSocket reconnects (useMessages effect deps don't re-trigger on reconnect).
|
|
89
87
|
const channels = useMemo(() => {
|
|
90
88
|
if (!configured) return undefined;
|
|
91
89
|
const serverChannels = config?.channels;
|