@agent-relay/dashboard 2.0.86 → 2.0.88
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/{1028-53c5a7e2453505f8.js → 1028-ff682899d23dc669.js} +1 -1
- package/out/_next/static/chunks/3238-24c1e4b1cefe3c71.js +73 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/{page-3dffd65b6344f53e.js → page-f33c6214e21ccfdd.js} +1 -1
- package/out/_next/static/chunks/app/{page-09ce10603ad9a251.js → page-2e38080856c3c293.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/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 +4 -3
- package/src/providers/RelayConfigProvider.tsx +15 -0
- package/src/providers/SendProvider.tsx +38 -4
- package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +0 -73
- /package/out/_next/static/{2BWmI-66Pm2lpI_cbat7z → hDG4FHMjeVX6ES941qVjL}/_buildManifest.js +0 -0
- /package/out/_next/static/{2BWmI-66Pm2lpI_cbat7z → hDG4FHMjeVX6ES941qVjL}/_ssgManifest.js +0 -0
|
@@ -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,
|
|
@@ -25,6 +25,7 @@ import { useRelayConfigStatus } from './RelayConfigProvider';
|
|
|
25
25
|
import { isDashboardVariant } from '../lib/identity';
|
|
26
26
|
import {
|
|
27
27
|
normalizeRelayDmMessageTargets,
|
|
28
|
+
getRelayDmParticipantName,
|
|
28
29
|
} from '../lib/relaycastMessageAdapters';
|
|
29
30
|
import { playNotificationSound } from './SettingsProvider';
|
|
30
31
|
import { useSettings } from './SettingsProvider';
|
|
@@ -470,7 +471,7 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
470
471
|
const currentUserName = currentUser?.displayName.toLowerCase();
|
|
471
472
|
for (const conversation of relayDMsState.conversations) {
|
|
472
473
|
for (const participant of conversation.participants) {
|
|
473
|
-
const name =
|
|
474
|
+
const name = getRelayDmParticipantName(participant);
|
|
474
475
|
if (!name) continue;
|
|
475
476
|
const lowered = name.toLowerCase();
|
|
476
477
|
if (currentUserName && lowered === currentUserName) continue;
|
|
@@ -508,12 +509,12 @@ function MessageProviderInner({ children, data, rawData: _rawData, enableReactio
|
|
|
508
509
|
if (!conversation.unreadCount) continue;
|
|
509
510
|
|
|
510
511
|
const match = conversation.participants.find((p) => {
|
|
511
|
-
const name =
|
|
512
|
+
const name = getRelayDmParticipantName(p);
|
|
512
513
|
if (!name) return false;
|
|
513
514
|
const lowered = name.toLowerCase();
|
|
514
515
|
return lowered !== currentUserName && !agentNames.has(lowered);
|
|
515
516
|
});
|
|
516
|
-
const participantName = match ? (
|
|
517
|
+
const participantName = match ? getRelayDmParticipantName(match) : null;
|
|
517
518
|
|
|
518
519
|
if (participantName) {
|
|
519
520
|
counts[participantName] = (counts[participantName] || 0) + conversation.unreadCount;
|
|
@@ -9,6 +9,7 @@ interface RelayConfigResponse {
|
|
|
9
9
|
apiKey?: string;
|
|
10
10
|
agentToken?: string;
|
|
11
11
|
agentName?: string | null;
|
|
12
|
+
channels?: string[];
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface RelayConfigProviderProps {
|
|
@@ -31,6 +32,9 @@ export function useRelayConfigStatus(): RelayConfigStatus {
|
|
|
31
32
|
return useContext(RelayConfigStatusContext);
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/** Default channels the dashboard agent should subscribe to via WebSocket */
|
|
36
|
+
const DEFAULT_CHANNELS = ['general'];
|
|
37
|
+
|
|
34
38
|
export function RelayConfigProvider({ children }: RelayConfigProviderProps) {
|
|
35
39
|
const [config, setConfig] = useState<RelayConfigResponse | null>(null);
|
|
36
40
|
const [loaded, setLoaded] = useState(false);
|
|
@@ -79,12 +83,23 @@ export function RelayConfigProvider({ children }: RelayConfigProviderProps) {
|
|
|
79
83
|
};
|
|
80
84
|
}, [configured, config]);
|
|
81
85
|
|
|
86
|
+
// Channels to auto-subscribe on WebSocket connect/reconnect.
|
|
87
|
+
const channels = useMemo(() => {
|
|
88
|
+
if (!configured) return undefined;
|
|
89
|
+
const serverChannels = config?.channels;
|
|
90
|
+
if (Array.isArray(serverChannels) && serverChannels.length > 0) {
|
|
91
|
+
return serverChannels;
|
|
92
|
+
}
|
|
93
|
+
return DEFAULT_CHANNELS;
|
|
94
|
+
}, [configured, config?.channels]);
|
|
95
|
+
|
|
82
96
|
return (
|
|
83
97
|
<RelayConfigStatusContext.Provider value={{ configured, loading: !loaded, agentName: config?.agentName ?? null }}>
|
|
84
98
|
<RelayProvider
|
|
85
99
|
baseUrl={providerConfig.baseUrl}
|
|
86
100
|
apiKey={providerConfig.apiKey}
|
|
87
101
|
agentToken={providerConfig.agentToken}
|
|
102
|
+
channels={channels}
|
|
88
103
|
>
|
|
89
104
|
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
90
105
|
{children as any}
|
|
@@ -156,9 +156,18 @@ export function SendProvider({ children, localMessages, localUsername }: SendPro
|
|
|
156
156
|
}, [localMessages, selectedChannelId, effectiveActiveWorkspaceId, currentUser?.displayName, relayAgentName]);
|
|
157
157
|
|
|
158
158
|
const effectiveChannelMessages = useMemo(() => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
if (usingRelayChannelMessages) {
|
|
160
|
+
// Prefer relay SDK messages; fall back to server-fetched messages when
|
|
161
|
+
// the relay hook returns empty (e.g. agent hasn't joined the channel yet).
|
|
162
|
+
if (relayMappedChannelMessages.length > 0) {
|
|
163
|
+
return sortChannelMessagesChronologically(relayMappedChannelMessages);
|
|
164
|
+
}
|
|
165
|
+
if (channelMessages.length > 0) {
|
|
166
|
+
return sortChannelMessagesChronologically(channelMessages);
|
|
167
|
+
}
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
const sourceMessages = channelMessages.length > 0 ? channelMessages : localChannelMessages;
|
|
162
171
|
return sortChannelMessagesChronologically(sourceMessages);
|
|
163
172
|
}, [usingRelayChannelMessages, relayMappedChannelMessages, channelMessages, localChannelMessages]);
|
|
164
173
|
|
|
@@ -252,7 +261,12 @@ export function SendProvider({ children, localMessages, localUsername }: SendPro
|
|
|
252
261
|
if (isWorkspaceFeaturesEnabled && !effectiveActiveWorkspaceId) return;
|
|
253
262
|
|
|
254
263
|
if (relayConfigured && selectedChannelId.startsWith('#')) {
|
|
255
|
-
|
|
264
|
+
// When the relay SDK has messages, use them directly.
|
|
265
|
+
// Otherwise, keep any server-fetched messages we already have — do NOT
|
|
266
|
+
// overwrite them with the empty relay array on every re-render.
|
|
267
|
+
if (relayMappedChannelMessages.length > 0) {
|
|
268
|
+
setChannelMessages(relayMappedChannelMessages);
|
|
269
|
+
}
|
|
256
270
|
setHasMoreMessages(false);
|
|
257
271
|
setChannelUnreadState(undefined);
|
|
258
272
|
setChannelsList(prev =>
|
|
@@ -260,6 +274,26 @@ export function SendProvider({ children, localMessages, localUsername }: SendPro
|
|
|
260
274
|
c.id === selectedChannelId ? { ...c, unreadCount: 0, hasMentions: false } : c
|
|
261
275
|
)
|
|
262
276
|
);
|
|
277
|
+
|
|
278
|
+
// Fallback: when the relay SDK hook returns empty (e.g. dashboard agent
|
|
279
|
+
// hasn't joined the channel), fetch from the server API which uses the
|
|
280
|
+
// workspace API key and has full read access to all channels.
|
|
281
|
+
if (relayMappedChannelMessages.length === 0 && !relayMessagesState.loading && !fetchedChannelsRef.current.has(selectedChannelId)) {
|
|
282
|
+
const channelToFetch = selectedChannelId;
|
|
283
|
+
fetchedChannelsRef.current.add(channelToFetch);
|
|
284
|
+
(async () => {
|
|
285
|
+
try {
|
|
286
|
+
const { getMessages } = await import('../components/channels');
|
|
287
|
+
const response = await getMessages(effectiveActiveWorkspaceId || 'local', channelToFetch, { limit: 200 });
|
|
288
|
+
const sortedMessages = sortChannelMessagesChronologically(response.messages);
|
|
289
|
+
setChannelMessageMap(prev => ({ ...prev, [channelToFetch]: sortedMessages }));
|
|
290
|
+
setChannelMessages(sortedMessages);
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.error('Failed to fetch channel messages fallback:', err);
|
|
293
|
+
fetchedChannelsRef.current.delete(channelToFetch);
|
|
294
|
+
}
|
|
295
|
+
})();
|
|
296
|
+
}
|
|
263
297
|
return;
|
|
264
298
|
}
|
|
265
299
|
|