@agent-relay/dashboard 2.0.82 → 2.0.84
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-da5d75e35d1420f1.js +1 -0
- package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
- package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
- package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
- package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
- package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
- package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
- package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
- package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
- package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
- package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
- package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
- package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
- package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
- package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
- package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
- package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
- package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
- package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
- package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
- package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
- package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
- package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
- package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
- package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
- package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
- package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
- package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
- package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
- package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
- package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
- package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
- package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
- package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
- package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
- package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
- package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
- package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
- package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
- package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
- package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
- package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
- package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
- package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
- package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
- package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
- package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
- package/out/about.html +2 -2
- package/out/about.txt +2 -2
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +2 -2
- 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 +3 -3
- 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 +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +2 -2
- package/out/changelog.html +2 -2
- package/out/changelog.txt +2 -2
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +2 -2
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +2 -2
- package/out/contact.html +2 -2
- package/out/contact.txt +2 -2
- package/out/dev/cli-tools.html +1 -0
- package/out/dev/cli-tools.txt +7 -0
- package/out/dev/log-viewer.html +23 -0
- package/out/dev/log-viewer.txt +7 -0
- package/out/docs.html +2 -2
- package/out/docs.txt +2 -2
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +2 -2
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +2 -2
- package/out/privacy.html +2 -2
- package/out/privacy.txt +2 -2
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +2 -2
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +2 -2
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +2 -2
- package/out/providers.html +1 -1
- package/out/providers.txt +2 -2
- package/out/security.html +2 -2
- package/out/security.txt +2 -2
- package/out/signup.html +2 -2
- package/out/signup.txt +2 -2
- package/out/terms.html +2 -2
- package/out/terms.txt +2 -2
- package/package.json +5 -1
- package/src/adapters/DashboardConfigProvider.tsx +56 -0
- package/src/adapters/cloudFetchAdapter.ts +278 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/types.ts +508 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
- package/src/app/app/onboarding/page.tsx +870 -170
- package/src/app/cloud/link/page.tsx +14 -6
- package/src/app/connect-repos/page.tsx +9 -3
- package/src/app/dev/cli-tools/page.tsx +130 -0
- package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
- package/src/app/dev/log-viewer/fixtures.ts +110 -0
- package/src/app/dev/log-viewer/page.tsx +288 -0
- package/src/app/history/page.tsx +28 -12
- package/src/app/page.tsx +1 -1
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
- package/src/components/AgentCard.tsx +4 -4
- package/src/components/AgentLogPreview.tsx +2 -38
- package/src/components/App.tsx +441 -2624
- package/src/components/CliToolHarness.test.tsx +83 -0
- package/src/components/CliToolHarness.tsx +292 -0
- package/src/components/CoordinatorPanel.tsx +13 -6
- package/src/components/LogViewer.tsx +2 -42
- package/src/components/ProviderAuthFlow.tsx +201 -81
- package/src/components/ProvisioningProgress.tsx +1 -1
- package/src/components/ReactionChips.tsx +2 -1
- package/src/components/SpawnModal.test.tsx +51 -18
- package/src/components/SpawnModal.tsx +175 -207
- package/src/components/TerminalProviderSetup.tsx +1 -1
- package/src/components/ThreadPanel.tsx +2 -0
- package/src/components/WorkspaceContext.tsx +7 -19
- package/src/components/XTermLogViewer.tsx +190 -27
- package/src/components/channels/ChannelMessageList.tsx +94 -4
- package/src/components/channels/ChannelViewV1.tsx +35 -11
- package/src/components/channels/api.ts +21 -20
- package/src/components/channels/types.ts +16 -0
- package/src/components/hooks/index.ts +0 -19
- package/src/components/hooks/useMessages.test.ts +80 -0
- package/src/components/hooks/useMessages.ts +13 -4
- package/src/components/hooks/useOrchestrator.ts +1 -1
- package/src/components/hooks/usePresence.ts +45 -6
- package/src/components/hooks/useThread.ts +83 -46
- package/src/components/hooks/useTrajectory.ts +62 -5
- package/src/components/hooks/useWebSocket.test.ts +358 -0
- package/src/components/hooks/useWebSocket.ts +243 -5
- package/src/components/index.ts +2 -14
- package/src/components/layout/Header.tsx +9 -15
- package/src/components/layout/Sidebar.tsx +1 -8
- package/src/components/settings/SettingsPage.tsx +108 -47
- package/src/components/settings/index.ts +0 -3
- package/src/landing/blogData.ts +1 -1
- package/src/lib/agent-merge.test.ts +2 -2
- package/src/lib/api.ts +8 -38
- package/src/lib/identity.test.ts +139 -0
- package/src/lib/identity.ts +48 -0
- package/src/lib/relaycastMessageAdapters.test.ts +182 -0
- package/src/lib/relaycastMessageAdapters.ts +105 -0
- package/src/lib/sanitize-logs.test.ts +227 -0
- package/src/lib/sanitize-logs.ts +202 -0
- package/src/providers/AgentProvider.tsx +799 -0
- package/src/providers/ChannelProvider.tsx +528 -0
- package/src/providers/CloudWorkspaceProvider.tsx +402 -0
- package/src/providers/MessageProvider.tsx +875 -0
- package/src/providers/RelayConfigProvider.tsx +94 -0
- package/src/providers/SendProvider.tsx +497 -0
- package/src/providers/SettingsProvider.tsx +247 -0
- package/src/providers/index.ts +26 -0
- package/src/types/index.ts +10 -10
- package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
- package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
- package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
- package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
- package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
- package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
- package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
- package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
- package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
- package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
- package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
- package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
- package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
- package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
- package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
- package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
- package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
- package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
- package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
- package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
- package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
- package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
- package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
- package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
- package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
- package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
- package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
- package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
- package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
- package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
- package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
- package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
- package/src/components/BillingResult.tsx +0 -447
- package/src/components/CloudSessionProvider.tsx +0 -130
- package/src/components/SessionExpiredModal.tsx +0 -128
- package/src/components/WorkspaceStatusIndicator.tsx +0 -396
- package/src/components/hooks/useSession.ts +0 -209
- package/src/components/hooks/useWorkspaceMembers.ts +0 -132
- package/src/components/hooks/useWorkspaceStatus.ts +0 -237
- package/src/components/settings/BillingSettingsPanel.tsx +0 -564
- package/src/components/settings/TeamSettingsPanel.tsx +0 -560
- package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
- package/src/lib/cloudApi.ts +0 -893
- /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useThread as useRelayThread,
|
|
4
|
+
useReply as useRelayReply,
|
|
5
|
+
} from '@relaycast/react';
|
|
6
|
+
import type { MessageWithMeta } from '@relaycast/sdk';
|
|
2
7
|
import type { Message } from '../../types';
|
|
3
8
|
import { api } from '../../lib/api';
|
|
9
|
+
import { useRelayConfigStatus } from '../../providers/RelayConfigProvider';
|
|
4
10
|
|
|
5
11
|
interface UseThreadOptions {
|
|
6
12
|
threadId: string | null;
|
|
@@ -19,118 +25,149 @@ interface UseThreadReturn {
|
|
|
19
25
|
addReply: (reply: Message) => void;
|
|
20
26
|
}
|
|
21
27
|
|
|
28
|
+
function toMessage(msg: MessageWithMeta): Message {
|
|
29
|
+
return {
|
|
30
|
+
id: msg.id,
|
|
31
|
+
from: msg.agentName ?? '',
|
|
32
|
+
to: '*',
|
|
33
|
+
content: msg.text,
|
|
34
|
+
timestamp: msg.createdAt ?? new Date().toISOString(),
|
|
35
|
+
replyCount: msg.replyCount ?? 0,
|
|
36
|
+
reactions: msg.reactions ?? [],
|
|
37
|
+
isRead: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
22
41
|
export function useThread({ threadId, fallbackMessages }: UseThreadOptions): UseThreadReturn {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
const { configured: relayConfigured } = useRelayConfigStatus();
|
|
43
|
+
|
|
44
|
+
// SDK hooks — always called (hooks can't be conditional)
|
|
45
|
+
const relayThread = useRelayThread(threadId ?? '');
|
|
46
|
+
const { reply: relayReply } = useRelayReply();
|
|
47
|
+
|
|
48
|
+
// REST / client-side fallback state
|
|
49
|
+
const [restParent, setRestParent] = useState<Message | null>(null);
|
|
50
|
+
const [restReplies, setRestReplies] = useState<Message[]>([]);
|
|
51
|
+
const [restLoading, setRestLoading] = useState(false);
|
|
26
52
|
const [hasMore, setHasMore] = useState(false);
|
|
27
53
|
const [cursor, setCursor] = useState<string | undefined>();
|
|
28
54
|
const [useFallback, setUseFallback] = useState(false);
|
|
29
|
-
|
|
30
|
-
// Use a ref to track the active threadId for cancellation of loadMore
|
|
31
55
|
const activeThreadIdRef = useRef<string | null>(null);
|
|
32
56
|
|
|
33
|
-
//
|
|
57
|
+
// REST / fallback fetch when Relaycast is not configured
|
|
34
58
|
useEffect(() => {
|
|
35
59
|
activeThreadIdRef.current = threadId;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
setReplies([]);
|
|
60
|
+
if (!threadId || relayConfigured) {
|
|
61
|
+
setRestParent(null);
|
|
62
|
+
setRestReplies([]);
|
|
40
63
|
setHasMore(false);
|
|
41
64
|
setCursor(undefined);
|
|
42
65
|
setUseFallback(false);
|
|
43
|
-
|
|
66
|
+
setRestLoading(false);
|
|
44
67
|
return;
|
|
45
68
|
}
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
setReplies([]);
|
|
70
|
+
setRestParent(null);
|
|
71
|
+
setRestReplies([]);
|
|
50
72
|
setHasMore(false);
|
|
51
73
|
setCursor(undefined);
|
|
52
74
|
setUseFallback(false);
|
|
53
75
|
|
|
54
76
|
let cancelled = false;
|
|
55
|
-
|
|
77
|
+
setRestLoading(true);
|
|
56
78
|
|
|
57
|
-
|
|
79
|
+
const loadThread = async () => {
|
|
80
|
+
const result = await api.getThread(threadId, { limit: 50 });
|
|
58
81
|
if (cancelled) return;
|
|
59
|
-
|
|
82
|
+
setRestLoading(false);
|
|
60
83
|
|
|
61
84
|
if (result.success && result.data) {
|
|
62
85
|
setUseFallback(false);
|
|
63
86
|
const { parent, replies: fetchedReplies, nextCursor } = result.data;
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
setRestParent(parent as Message);
|
|
88
|
+
setRestReplies(fetchedReplies);
|
|
66
89
|
setHasMore(!!nextCursor);
|
|
67
90
|
setCursor(nextCursor);
|
|
68
91
|
} else {
|
|
69
|
-
// API not available — fall back to client-side messages
|
|
70
92
|
setUseFallback(true);
|
|
71
93
|
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
return () => {
|
|
75
|
-
cancelled = true;
|
|
76
94
|
};
|
|
77
|
-
}, [threadId]);
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
void loadThread();
|
|
97
|
+
return () => { cancelled = true; };
|
|
98
|
+
}, [threadId, relayConfigured]);
|
|
99
|
+
|
|
100
|
+
// --- Derive final values based on mode ---
|
|
101
|
+
|
|
102
|
+
const isRelayMode = relayConfigured && !!threadId;
|
|
103
|
+
|
|
104
|
+
// Relay path: map SDK MessageWithMeta → dashboard Message
|
|
105
|
+
const relayParent = isRelayMode && relayThread.parent ? toMessage(relayThread.parent) : null;
|
|
106
|
+
const relayReplies = isRelayMode ? relayThread.replies.map(toMessage) : [];
|
|
107
|
+
|
|
108
|
+
// Fallback path: use client-side messages
|
|
109
|
+
const fallbackParent = useFallback
|
|
83
110
|
? (fallbackMessages?.find((m) => m.id === threadId)
|
|
84
111
|
?? fallbackMessages?.filter((m) => m.thread === threadId)
|
|
85
112
|
.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())[0]
|
|
86
113
|
?? null)
|
|
87
|
-
:
|
|
114
|
+
: null;
|
|
115
|
+
const fallbackReplies = useFallback
|
|
116
|
+
? (fallbackMessages?.filter((m) => m.thread === threadId && m.id !== fallbackParent?.id) ?? [])
|
|
117
|
+
: [];
|
|
88
118
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
// Pick the active source
|
|
120
|
+
const parentMessage = isRelayMode ? relayParent : (useFallback ? fallbackParent : restParent);
|
|
121
|
+
const replies = isRelayMode ? relayReplies : (useFallback ? fallbackReplies : restReplies);
|
|
122
|
+
const isLoading = isRelayMode ? relayThread.loading : restLoading;
|
|
92
123
|
|
|
93
124
|
const loadMore = useCallback(async () => {
|
|
94
|
-
if (!threadId || !hasMore || !cursor || useFallback) return;
|
|
125
|
+
if (relayConfigured || !threadId || !hasMore || !cursor || useFallback) return;
|
|
95
126
|
const loadingThreadId = threadId;
|
|
96
|
-
|
|
127
|
+
setRestLoading(true);
|
|
97
128
|
const result = await api.getThread(threadId, { cursor, limit: 50 });
|
|
98
|
-
// If thread changed while loading, discard the stale response
|
|
99
129
|
if (activeThreadIdRef.current !== loadingThreadId) return;
|
|
100
|
-
|
|
130
|
+
setRestLoading(false);
|
|
101
131
|
if (result.success && result.data) {
|
|
102
|
-
|
|
132
|
+
setRestReplies((prev) => [...result.data!.replies, ...prev]);
|
|
103
133
|
setHasMore(!!result.data.nextCursor);
|
|
104
134
|
setCursor(result.data.nextCursor);
|
|
105
135
|
}
|
|
106
|
-
}, [threadId, hasMore, cursor, useFallback]);
|
|
136
|
+
}, [threadId, hasMore, cursor, useFallback, relayConfigured]);
|
|
107
137
|
|
|
108
138
|
const sendReply = useCallback(
|
|
109
139
|
async (text: string): Promise<boolean> => {
|
|
110
140
|
if (!threadId) return false;
|
|
141
|
+
if (relayConfigured) {
|
|
142
|
+
try {
|
|
143
|
+
await relayReply(threadId, text);
|
|
144
|
+
return true;
|
|
145
|
+
} catch {
|
|
146
|
+
// Fall through to REST fallback.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
111
149
|
const result = await api.postReply(threadId, text);
|
|
112
150
|
if (result.success && result.data) {
|
|
113
|
-
|
|
151
|
+
setRestReplies((prev) => [...prev, result.data!]);
|
|
114
152
|
return true;
|
|
115
153
|
}
|
|
116
154
|
return false;
|
|
117
155
|
},
|
|
118
|
-
[threadId],
|
|
156
|
+
[threadId, relayConfigured, relayReply],
|
|
119
157
|
);
|
|
120
158
|
|
|
121
159
|
const addReply = useCallback((reply: Message) => {
|
|
122
|
-
|
|
123
|
-
// Deduplicate
|
|
160
|
+
setRestReplies((prev) => {
|
|
124
161
|
if (prev.some((m) => m.id === reply.id)) return prev;
|
|
125
162
|
return [...prev, reply];
|
|
126
163
|
});
|
|
127
164
|
}, []);
|
|
128
165
|
|
|
129
166
|
return {
|
|
130
|
-
parentMessage
|
|
131
|
-
replies
|
|
167
|
+
parentMessage,
|
|
168
|
+
replies,
|
|
132
169
|
isLoading,
|
|
133
|
-
hasMore: useFallback ? false : hasMore,
|
|
170
|
+
hasMore: useFallback || isRelayMode ? false : hasMore,
|
|
134
171
|
loadMore,
|
|
135
172
|
sendReply,
|
|
136
173
|
addReply,
|
|
@@ -49,6 +49,61 @@ interface UseTrajectoryResult {
|
|
|
49
49
|
selectedTrajectoryId: string | null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function extractHistory(data: unknown): { success: boolean; trajectories: TrajectoryHistoryEntry[]; error?: string } {
|
|
53
|
+
if (!data || typeof data !== 'object') {
|
|
54
|
+
return { success: false, trajectories: [], error: 'Invalid history response' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const raw = data as { success?: boolean; trajectories?: TrajectoryHistoryEntry[] };
|
|
58
|
+
const hasSuccess = Object.prototype.hasOwnProperty.call(raw, 'success');
|
|
59
|
+
|
|
60
|
+
if (Array.isArray((data as { trajectories?: unknown }).trajectories)) {
|
|
61
|
+
const trajectories = (data as { trajectories: TrajectoryHistoryEntry[] }).trajectories;
|
|
62
|
+
const isSuccess = hasSuccess ? raw.success === true : true;
|
|
63
|
+
return { success: Boolean(isSuccess), trajectories: isSuccess ? trajectories : [] };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (Array.isArray(data)) {
|
|
67
|
+
return { success: true, trajectories: data as TrajectoryHistoryEntry[] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
trajectories: [],
|
|
73
|
+
error: 'History payload missing trajectories',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractSteps(data: unknown): { success: boolean; steps: TrajectoryStep[]; error?: string } {
|
|
78
|
+
if (!data || typeof data !== 'object') {
|
|
79
|
+
return { success: false, steps: [], error: 'Invalid steps response' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const raw = data as { success?: boolean; steps?: TrajectoryStep[]; error?: string };
|
|
83
|
+
const hasSuccess = Object.prototype.hasOwnProperty.call(raw, 'success');
|
|
84
|
+
const hasSteps = Array.isArray(raw.steps);
|
|
85
|
+
|
|
86
|
+
if (hasSteps) {
|
|
87
|
+
const isSuccess = hasSuccess ? raw.success === true : true;
|
|
88
|
+
const steps = raw.steps ?? [];
|
|
89
|
+
return {
|
|
90
|
+
success: Boolean(isSuccess),
|
|
91
|
+
steps: isSuccess ? steps : [],
|
|
92
|
+
error: !isSuccess ? raw.error : undefined,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (Array.isArray(data)) {
|
|
97
|
+
return { success: true, steps: data as TrajectoryStep[] };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
steps: [],
|
|
103
|
+
error: 'Steps payload missing steps',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
52
107
|
export function useTrajectory(options: UseTrajectoryOptions = {}): UseTrajectoryResult {
|
|
53
108
|
const {
|
|
54
109
|
pollInterval = 2000,
|
|
@@ -103,9 +158,10 @@ export function useTrajectory(options: UseTrajectoryOptions = {}): UseTrajectory
|
|
|
103
158
|
: getApiUrl('/api/trajectory/history');
|
|
104
159
|
const response = await fetch(url, { credentials: 'include' });
|
|
105
160
|
const data = await response.json();
|
|
161
|
+
const normalized = extractHistory(data);
|
|
106
162
|
|
|
107
|
-
if (
|
|
108
|
-
setHistory(
|
|
163
|
+
if (normalized.success) {
|
|
164
|
+
setHistory(normalized.trajectories || []);
|
|
109
165
|
}
|
|
110
166
|
} catch (err: any) {
|
|
111
167
|
console.error('[useTrajectory] History fetch error:', err);
|
|
@@ -129,6 +185,7 @@ export function useTrajectory(options: UseTrajectoryOptions = {}): UseTrajectory
|
|
|
129
185
|
|
|
130
186
|
const response = await fetch(url, { credentials: 'include' });
|
|
131
187
|
const data = await response.json();
|
|
188
|
+
const normalized = extractSteps(data);
|
|
132
189
|
|
|
133
190
|
// Only update state if this is still the most recent request
|
|
134
191
|
// Check both request counter AND trajectory ID for double protection
|
|
@@ -141,11 +198,11 @@ export function useTrajectory(options: UseTrajectoryOptions = {}): UseTrajectory
|
|
|
141
198
|
return;
|
|
142
199
|
}
|
|
143
200
|
|
|
144
|
-
if (
|
|
145
|
-
setSteps(
|
|
201
|
+
if (normalized.success) {
|
|
202
|
+
setSteps(normalized.steps || []);
|
|
146
203
|
setError(null);
|
|
147
204
|
} else {
|
|
148
|
-
setError(
|
|
205
|
+
setError(normalized.error || 'Failed to fetch trajectory steps');
|
|
149
206
|
}
|
|
150
207
|
} catch (err: any) {
|
|
151
208
|
// Only update error state if this is still the current request
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { DashboardData } from './useWebSocket';
|
|
3
|
+
import { applyBrokerEvent } from './useWebSocket';
|
|
4
|
+
|
|
5
|
+
function emptyState(): DashboardData {
|
|
6
|
+
return {
|
|
7
|
+
agents: [],
|
|
8
|
+
messages: [],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('applyBrokerEvent', () => {
|
|
13
|
+
it('adds relay_inbound DM to messages', () => {
|
|
14
|
+
const next = applyBrokerEvent(emptyState(), {
|
|
15
|
+
kind: 'relay_inbound',
|
|
16
|
+
event_id: 'evt_1',
|
|
17
|
+
from: 'Dashboard',
|
|
18
|
+
target: 'Lead',
|
|
19
|
+
body: 'hello',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(next?.messages).toHaveLength(1);
|
|
23
|
+
expect(next?.messages[0]).toMatchObject({
|
|
24
|
+
id: 'evt_1',
|
|
25
|
+
from: 'Dashboard',
|
|
26
|
+
to: 'Lead',
|
|
27
|
+
content: 'hello',
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('skips relay_inbound channel messages (handled by useChannels)', () => {
|
|
32
|
+
const next = applyBrokerEvent(emptyState(), {
|
|
33
|
+
kind: 'relay_inbound',
|
|
34
|
+
event_id: 'evt_ch',
|
|
35
|
+
from: 'Hero',
|
|
36
|
+
target: '#general',
|
|
37
|
+
body: 'hello channel',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(next?.messages).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('bootstraps empty state when prev is null', () => {
|
|
44
|
+
const next = applyBrokerEvent(null, {
|
|
45
|
+
kind: 'relay_inbound',
|
|
46
|
+
event_id: 'evt_null',
|
|
47
|
+
from: 'Dashboard',
|
|
48
|
+
target: 'Lead',
|
|
49
|
+
body: 'hello',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(next).not.toBeNull();
|
|
53
|
+
expect(next?.messages).toHaveLength(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('skips relay_inbound with missing fields', () => {
|
|
57
|
+
const next = applyBrokerEvent(emptyState(), {
|
|
58
|
+
kind: 'relay_inbound',
|
|
59
|
+
event_id: 'evt_bad',
|
|
60
|
+
from: '',
|
|
61
|
+
target: 'Lead',
|
|
62
|
+
body: 'hello',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(next?.messages).toHaveLength(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// --- Delivery Status ---
|
|
69
|
+
|
|
70
|
+
describe('delivery status events', () => {
|
|
71
|
+
it('delivery_verified sets message status to acked by event_id', () => {
|
|
72
|
+
const state: DashboardData = {
|
|
73
|
+
agents: [],
|
|
74
|
+
messages: [
|
|
75
|
+
{ id: 'evt_1', from: 'Dashboard', to: 'Lead', content: 'hello', timestamp: '2024-01-01', status: 'sending' },
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const next = applyBrokerEvent(state, {
|
|
80
|
+
kind: 'delivery_verified',
|
|
81
|
+
name: 'Lead',
|
|
82
|
+
delivery_id: 'del_1',
|
|
83
|
+
event_id: 'evt_1',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(next?.messages[0]?.status).toBe('acked');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('delivery_verified does not modify messages with different event_id', () => {
|
|
90
|
+
const state: DashboardData = {
|
|
91
|
+
agents: [],
|
|
92
|
+
messages: [
|
|
93
|
+
{ id: 'evt_other', from: 'Dashboard', to: 'Lead', content: 'hello', timestamp: '2024-01-01', status: 'sending' },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const next = applyBrokerEvent(state, {
|
|
98
|
+
kind: 'delivery_verified',
|
|
99
|
+
name: 'Lead',
|
|
100
|
+
delivery_id: 'del_1',
|
|
101
|
+
event_id: 'evt_1',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(next?.messages[0]?.status).toBe('sending');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('delivery_verified with missing event_id returns prev state', () => {
|
|
108
|
+
const state = emptyState();
|
|
109
|
+
const next = applyBrokerEvent(state, {
|
|
110
|
+
kind: 'delivery_verified',
|
|
111
|
+
name: 'Lead',
|
|
112
|
+
delivery_id: 'del_1',
|
|
113
|
+
});
|
|
114
|
+
expect(next).toBe(state);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('delivery_failed sets message status to failed by event_id', () => {
|
|
118
|
+
const state: DashboardData = {
|
|
119
|
+
agents: [],
|
|
120
|
+
messages: [
|
|
121
|
+
{ id: 'evt_2', from: 'Dashboard', to: 'Lead', content: 'hello', timestamp: '2024-01-01', status: 'sending' },
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const next = applyBrokerEvent(state, {
|
|
126
|
+
kind: 'delivery_failed',
|
|
127
|
+
name: 'Lead',
|
|
128
|
+
delivery_id: 'del_2',
|
|
129
|
+
event_id: 'evt_2',
|
|
130
|
+
reason: 'timeout',
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(next?.messages[0]?.status).toBe('failed');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('delivery_failed with missing event_id returns prev state', () => {
|
|
137
|
+
const state = emptyState();
|
|
138
|
+
const next = applyBrokerEvent(state, {
|
|
139
|
+
kind: 'delivery_failed',
|
|
140
|
+
name: 'Lead',
|
|
141
|
+
delivery_id: 'del_2',
|
|
142
|
+
reason: 'timeout',
|
|
143
|
+
});
|
|
144
|
+
expect(next).toBe(state);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// --- Thinking / Processing State ---
|
|
149
|
+
|
|
150
|
+
describe('thinking/processing state events', () => {
|
|
151
|
+
function stateWithAgent(name: string): DashboardData {
|
|
152
|
+
return {
|
|
153
|
+
agents: [{ name, status: 'online' }],
|
|
154
|
+
messages: [],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
it('delivery_ack sets agent isProcessing and processingStartedAt', () => {
|
|
159
|
+
const state = stateWithAgent('Lead');
|
|
160
|
+
const before = Date.now();
|
|
161
|
+
|
|
162
|
+
const next = applyBrokerEvent(state, {
|
|
163
|
+
kind: 'delivery_ack',
|
|
164
|
+
delivery_id: 'del_1',
|
|
165
|
+
name: 'Lead',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const agent = next?.agents.find((a) => a.name === 'Lead');
|
|
169
|
+
expect(agent?.isProcessing).toBe(true);
|
|
170
|
+
expect(agent?.processingStartedAt).toBeGreaterThanOrEqual(before);
|
|
171
|
+
expect(agent?.processingStartedAt).toBeLessThanOrEqual(Date.now());
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('delivery_active sets agent isProcessing and processingStartedAt', () => {
|
|
175
|
+
const state = stateWithAgent('Lead');
|
|
176
|
+
const before = Date.now();
|
|
177
|
+
|
|
178
|
+
const next = applyBrokerEvent(state, {
|
|
179
|
+
kind: 'delivery_active',
|
|
180
|
+
delivery_id: 'del_1',
|
|
181
|
+
name: 'Lead',
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const agent = next?.agents.find((a) => a.name === 'Lead');
|
|
185
|
+
expect(agent?.isProcessing).toBe(true);
|
|
186
|
+
expect(agent?.processingStartedAt).toBeGreaterThanOrEqual(before);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('delivery_ack with missing name field returns prev state', () => {
|
|
190
|
+
const state = stateWithAgent('Lead');
|
|
191
|
+
const next = applyBrokerEvent(state, {
|
|
192
|
+
kind: 'delivery_ack',
|
|
193
|
+
delivery_id: 'del_1',
|
|
194
|
+
});
|
|
195
|
+
expect(next).toBe(state);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('delivery_active with missing name field returns prev state', () => {
|
|
199
|
+
const state = stateWithAgent('Lead');
|
|
200
|
+
const next = applyBrokerEvent(state, {
|
|
201
|
+
kind: 'delivery_active',
|
|
202
|
+
delivery_id: 'del_1',
|
|
203
|
+
});
|
|
204
|
+
expect(next).toBe(state);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('delivery_ack does not modify unrelated agents', () => {
|
|
208
|
+
const state: DashboardData = {
|
|
209
|
+
agents: [
|
|
210
|
+
{ name: 'Lead', status: 'online' },
|
|
211
|
+
{ name: 'Helper', status: 'online' },
|
|
212
|
+
],
|
|
213
|
+
messages: [],
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const next = applyBrokerEvent(state, {
|
|
217
|
+
kind: 'delivery_ack',
|
|
218
|
+
delivery_id: 'del_1',
|
|
219
|
+
name: 'Lead',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(next?.agents.find((a) => a.name === 'Helper')?.isProcessing).toBeUndefined();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('agent_idle clears processingStartedAt and lastLogLine', () => {
|
|
226
|
+
const state: DashboardData = {
|
|
227
|
+
agents: [{ name: 'Lead', status: 'online', isProcessing: true, processingStartedAt: 12345, lastLogLine: 'some log' }],
|
|
228
|
+
messages: [],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const next = applyBrokerEvent(state, {
|
|
232
|
+
kind: 'agent_idle',
|
|
233
|
+
name: 'Lead',
|
|
234
|
+
idle_secs: 5,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const agent = next?.agents.find((a) => a.name === 'Lead');
|
|
238
|
+
expect(agent?.isProcessing).toBe(false);
|
|
239
|
+
expect(agent?.processingStartedAt).toBeUndefined();
|
|
240
|
+
expect(agent?.lastLogLine).toBeUndefined();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// --- Worker Stream Logs ---
|
|
245
|
+
|
|
246
|
+
describe('worker stream events', () => {
|
|
247
|
+
it('worker_stream sets lastLogLine on the matching agent', () => {
|
|
248
|
+
const state: DashboardData = {
|
|
249
|
+
agents: [{ name: 'Lead', status: 'online', isProcessing: true }],
|
|
250
|
+
messages: [],
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const next = applyBrokerEvent(state, {
|
|
254
|
+
kind: 'worker_stream',
|
|
255
|
+
name: 'Lead',
|
|
256
|
+
stream: 'stdout',
|
|
257
|
+
chunk: 'Running tests...',
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(next?.agents.find((a) => a.name === 'Lead')?.lastLogLine).toBe('Running tests...');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('worker_stream does not modify unrelated agents', () => {
|
|
264
|
+
const state: DashboardData = {
|
|
265
|
+
agents: [
|
|
266
|
+
{ name: 'Lead', status: 'online' },
|
|
267
|
+
{ name: 'Helper', status: 'online' },
|
|
268
|
+
],
|
|
269
|
+
messages: [],
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const next = applyBrokerEvent(state, {
|
|
273
|
+
kind: 'worker_stream',
|
|
274
|
+
name: 'Lead',
|
|
275
|
+
stream: 'stdout',
|
|
276
|
+
chunk: 'log line',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(next?.agents.find((a) => a.name === 'Helper')?.lastLogLine).toBeUndefined();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('worker_stream with missing name returns prev state', () => {
|
|
283
|
+
const state = emptyState();
|
|
284
|
+
const next = applyBrokerEvent(state, {
|
|
285
|
+
kind: 'worker_stream',
|
|
286
|
+
stream: 'stdout',
|
|
287
|
+
chunk: 'text',
|
|
288
|
+
});
|
|
289
|
+
expect(next).toBe(state);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('worker_stream overwrites previous lastLogLine', () => {
|
|
293
|
+
const state: DashboardData = {
|
|
294
|
+
agents: [{ name: 'Lead', status: 'online', lastLogLine: 'old line' }],
|
|
295
|
+
messages: [],
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const next = applyBrokerEvent(state, {
|
|
299
|
+
kind: 'worker_stream',
|
|
300
|
+
name: 'Lead',
|
|
301
|
+
stream: 'stdout',
|
|
302
|
+
chunk: 'new line',
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
expect(next?.agents.find((a) => a.name === 'Lead')?.lastLogLine).toBe('new line');
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// --- Full Lifecycle Integration ---
|
|
310
|
+
|
|
311
|
+
describe('full delivery lifecycle', () => {
|
|
312
|
+
it('sending -> acked -> processing -> stream -> idle clears all state', () => {
|
|
313
|
+
let state: DashboardData = {
|
|
314
|
+
agents: [{ name: 'Lead', status: 'online' }],
|
|
315
|
+
messages: [
|
|
316
|
+
{ id: 'evt_lc', from: 'Dashboard', to: 'Lead', content: 'do stuff', timestamp: '2024-01-01', status: 'sending' },
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// 1. delivery_verified -> message becomes acked
|
|
321
|
+
state = applyBrokerEvent(state, {
|
|
322
|
+
kind: 'delivery_verified',
|
|
323
|
+
name: 'Lead',
|
|
324
|
+
delivery_id: 'del_lc',
|
|
325
|
+
event_id: 'evt_lc',
|
|
326
|
+
})!;
|
|
327
|
+
expect(state.messages[0]?.status).toBe('acked');
|
|
328
|
+
|
|
329
|
+
// 2. delivery_ack -> agent starts processing
|
|
330
|
+
state = applyBrokerEvent(state, {
|
|
331
|
+
kind: 'delivery_ack',
|
|
332
|
+
delivery_id: 'del_lc',
|
|
333
|
+
name: 'Lead',
|
|
334
|
+
})!;
|
|
335
|
+
expect(state.agents[0]?.isProcessing).toBe(true);
|
|
336
|
+
expect(state.agents[0]?.processingStartedAt).toBeDefined();
|
|
337
|
+
|
|
338
|
+
// 3. worker_stream -> log line appears
|
|
339
|
+
state = applyBrokerEvent(state, {
|
|
340
|
+
kind: 'worker_stream',
|
|
341
|
+
name: 'Lead',
|
|
342
|
+
stream: 'stdout',
|
|
343
|
+
chunk: 'Compiling...',
|
|
344
|
+
})!;
|
|
345
|
+
expect(state.agents[0]?.lastLogLine).toBe('Compiling...');
|
|
346
|
+
|
|
347
|
+
// 4. agent_idle -> processing state fully cleared
|
|
348
|
+
state = applyBrokerEvent(state, {
|
|
349
|
+
kind: 'agent_idle',
|
|
350
|
+
name: 'Lead',
|
|
351
|
+
idle_secs: 3,
|
|
352
|
+
})!;
|
|
353
|
+
expect(state.agents[0]?.isProcessing).toBe(false);
|
|
354
|
+
expect(state.agents[0]?.processingStartedAt).toBeUndefined();
|
|
355
|
+
expect(state.agents[0]?.lastLogLine).toBeUndefined();
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
});
|