@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.
Files changed (228) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/1028-da5d75e35d1420f1.js +1 -0
  3. package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
  4. package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
  5. package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
  6. package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
  7. package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
  8. package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
  9. package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
  10. package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
  11. package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
  12. package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
  13. package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
  14. package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
  15. package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
  16. package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
  17. package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
  18. package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
  19. package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
  20. package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
  21. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
  22. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
  23. package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
  24. package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
  25. package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
  26. package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
  27. package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
  28. package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
  29. package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
  30. package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
  31. package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
  32. package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
  33. package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
  34. package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
  35. package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
  36. package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
  37. package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
  38. package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
  39. package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
  40. package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
  41. package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
  42. package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
  43. package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
  44. package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
  45. package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
  46. package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
  47. package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
  48. package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
  49. package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
  50. package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
  51. package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
  52. package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
  53. package/out/about.html +2 -2
  54. package/out/about.txt +2 -2
  55. package/out/app/onboarding.html +1 -1
  56. package/out/app/onboarding.txt +2 -2
  57. package/out/app.html +1 -1
  58. package/out/app.txt +2 -2
  59. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
  60. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  61. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  62. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  63. package/out/blog.html +2 -2
  64. package/out/blog.txt +1 -1
  65. package/out/careers.html +2 -2
  66. package/out/careers.txt +2 -2
  67. package/out/changelog.html +2 -2
  68. package/out/changelog.txt +2 -2
  69. package/out/cloud/link.html +1 -1
  70. package/out/cloud/link.txt +2 -2
  71. package/out/complete-profile.html +2 -2
  72. package/out/complete-profile.txt +2 -2
  73. package/out/connect-repos.html +1 -1
  74. package/out/connect-repos.txt +2 -2
  75. package/out/contact.html +2 -2
  76. package/out/contact.txt +2 -2
  77. package/out/dev/cli-tools.html +1 -0
  78. package/out/dev/cli-tools.txt +7 -0
  79. package/out/dev/log-viewer.html +23 -0
  80. package/out/dev/log-viewer.txt +7 -0
  81. package/out/docs.html +2 -2
  82. package/out/docs.txt +2 -2
  83. package/out/history.html +1 -1
  84. package/out/history.txt +2 -2
  85. package/out/index.html +1 -1
  86. package/out/index.txt +2 -2
  87. package/out/login.html +2 -2
  88. package/out/login.txt +2 -2
  89. package/out/metrics.html +1 -1
  90. package/out/metrics.txt +2 -2
  91. package/out/pricing.html +2 -2
  92. package/out/pricing.txt +2 -2
  93. package/out/privacy.html +2 -2
  94. package/out/privacy.txt +2 -2
  95. package/out/providers/setup/claude.html +1 -1
  96. package/out/providers/setup/claude.txt +2 -2
  97. package/out/providers/setup/codex.html +1 -1
  98. package/out/providers/setup/codex.txt +2 -2
  99. package/out/providers/setup/cursor.html +1 -1
  100. package/out/providers/setup/cursor.txt +2 -2
  101. package/out/providers.html +1 -1
  102. package/out/providers.txt +2 -2
  103. package/out/security.html +2 -2
  104. package/out/security.txt +2 -2
  105. package/out/signup.html +2 -2
  106. package/out/signup.txt +2 -2
  107. package/out/terms.html +2 -2
  108. package/out/terms.txt +2 -2
  109. package/package.json +5 -1
  110. package/src/adapters/DashboardConfigProvider.tsx +56 -0
  111. package/src/adapters/cloudFetchAdapter.ts +278 -0
  112. package/src/adapters/index.ts +3 -0
  113. package/src/adapters/types.ts +508 -0
  114. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
  115. package/src/app/app/onboarding/page.tsx +870 -170
  116. package/src/app/cloud/link/page.tsx +14 -6
  117. package/src/app/connect-repos/page.tsx +9 -3
  118. package/src/app/dev/cli-tools/page.tsx +130 -0
  119. package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
  120. package/src/app/dev/log-viewer/fixtures.ts +110 -0
  121. package/src/app/dev/log-viewer/page.tsx +288 -0
  122. package/src/app/history/page.tsx +28 -12
  123. package/src/app/page.tsx +1 -1
  124. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
  125. package/src/components/AgentCard.tsx +4 -4
  126. package/src/components/AgentLogPreview.tsx +2 -38
  127. package/src/components/App.tsx +441 -2624
  128. package/src/components/CliToolHarness.test.tsx +83 -0
  129. package/src/components/CliToolHarness.tsx +292 -0
  130. package/src/components/CoordinatorPanel.tsx +13 -6
  131. package/src/components/LogViewer.tsx +2 -42
  132. package/src/components/ProviderAuthFlow.tsx +201 -81
  133. package/src/components/ProvisioningProgress.tsx +1 -1
  134. package/src/components/ReactionChips.tsx +2 -1
  135. package/src/components/SpawnModal.test.tsx +51 -18
  136. package/src/components/SpawnModal.tsx +175 -207
  137. package/src/components/TerminalProviderSetup.tsx +1 -1
  138. package/src/components/ThreadPanel.tsx +2 -0
  139. package/src/components/WorkspaceContext.tsx +7 -19
  140. package/src/components/XTermLogViewer.tsx +190 -27
  141. package/src/components/channels/ChannelMessageList.tsx +94 -4
  142. package/src/components/channels/ChannelViewV1.tsx +35 -11
  143. package/src/components/channels/api.ts +21 -20
  144. package/src/components/channels/types.ts +16 -0
  145. package/src/components/hooks/index.ts +0 -19
  146. package/src/components/hooks/useMessages.test.ts +80 -0
  147. package/src/components/hooks/useMessages.ts +13 -4
  148. package/src/components/hooks/useOrchestrator.ts +1 -1
  149. package/src/components/hooks/usePresence.ts +45 -6
  150. package/src/components/hooks/useThread.ts +83 -46
  151. package/src/components/hooks/useTrajectory.ts +62 -5
  152. package/src/components/hooks/useWebSocket.test.ts +358 -0
  153. package/src/components/hooks/useWebSocket.ts +243 -5
  154. package/src/components/index.ts +2 -14
  155. package/src/components/layout/Header.tsx +9 -15
  156. package/src/components/layout/Sidebar.tsx +1 -8
  157. package/src/components/settings/SettingsPage.tsx +108 -47
  158. package/src/components/settings/index.ts +0 -3
  159. package/src/landing/blogData.ts +1 -1
  160. package/src/lib/agent-merge.test.ts +2 -2
  161. package/src/lib/api.ts +8 -38
  162. package/src/lib/identity.test.ts +139 -0
  163. package/src/lib/identity.ts +48 -0
  164. package/src/lib/relaycastMessageAdapters.test.ts +182 -0
  165. package/src/lib/relaycastMessageAdapters.ts +105 -0
  166. package/src/lib/sanitize-logs.test.ts +227 -0
  167. package/src/lib/sanitize-logs.ts +202 -0
  168. package/src/providers/AgentProvider.tsx +799 -0
  169. package/src/providers/ChannelProvider.tsx +528 -0
  170. package/src/providers/CloudWorkspaceProvider.tsx +402 -0
  171. package/src/providers/MessageProvider.tsx +875 -0
  172. package/src/providers/RelayConfigProvider.tsx +94 -0
  173. package/src/providers/SendProvider.tsx +497 -0
  174. package/src/providers/SettingsProvider.tsx +247 -0
  175. package/src/providers/index.ts +26 -0
  176. package/src/types/index.ts +10 -10
  177. package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
  178. package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
  179. package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
  180. package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
  181. package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
  182. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
  183. package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
  184. package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
  185. package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
  186. package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
  187. package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
  188. package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
  189. package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
  190. package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
  191. package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
  192. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
  193. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
  194. package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
  195. package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
  196. package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
  197. package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
  198. package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
  199. package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
  200. package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
  201. package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
  202. package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
  203. package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
  204. package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
  205. package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
  206. package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
  207. package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
  208. package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
  209. package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
  210. package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
  211. package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
  212. package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
  213. package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
  214. package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
  215. package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
  216. package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
  217. package/src/components/BillingResult.tsx +0 -447
  218. package/src/components/CloudSessionProvider.tsx +0 -130
  219. package/src/components/SessionExpiredModal.tsx +0 -128
  220. package/src/components/WorkspaceStatusIndicator.tsx +0 -396
  221. package/src/components/hooks/useSession.ts +0 -209
  222. package/src/components/hooks/useWorkspaceMembers.ts +0 -132
  223. package/src/components/hooks/useWorkspaceStatus.ts +0 -237
  224. package/src/components/settings/BillingSettingsPanel.tsx +0 -564
  225. package/src/components/settings/TeamSettingsPanel.tsx +0 -560
  226. package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
  227. package/src/lib/cloudApi.ts +0 -893
  228. /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
@@ -0,0 +1,799 @@
1
+ /**
2
+ * Agent Provider
3
+ *
4
+ * Manages the combined agent list (AI agents + human users + local broker agents),
5
+ * project/agent merging, spawn/release operations, fleet, and decisions.
6
+ */
7
+
8
+ import React, { createContext, useContext, useState, useCallback, useEffect, useMemo, useRef } from 'react';
9
+ import type { Agent, Project, AgentSummary, ActivityEvent } from '../types';
10
+ import type { SpawnConfig } from '../components/SpawnModal';
11
+ import type { Decision } from '../components/DecisionQueue';
12
+ import type { ServerInfo } from '../components/ServerCard';
13
+ import type { TaskCreateRequest } from '../components/CommandPalette';
14
+ import { PRIORITY_CONFIG } from '../components/CommandPalette';
15
+ import { useAgents as useAgentsHook } from '../components/hooks/useAgents';
16
+ import { useRecentRepos } from '../components/hooks/useRecentRepos';
17
+ import { useWorkspaceRepos } from '../components/hooks/useWorkspaceRepos';
18
+ import { useCloudWorkspace } from './CloudWorkspaceProvider';
19
+ import { api, convertApiDecision, getCsrfToken } from '../lib/api';
20
+ import { mergeAgentsForDashboard } from '../lib/agent-merge';
21
+ import type { DashboardData } from '../components/hooks/useWebSocket';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Types
25
+ // ---------------------------------------------------------------------------
26
+
27
+ interface AgentContextValue {
28
+ // Core agent data
29
+ agents: Agent[];
30
+ combinedAgents: Agent[];
31
+ groups: ReturnType<typeof useAgentsHook>['groups'];
32
+ selectedAgent: Agent | null;
33
+ selectAgent: (name: string | null) => void;
34
+ searchQuery: string;
35
+ setSearchQuery: (q: string) => void;
36
+ totalCount: number;
37
+ onlineCount: number;
38
+ needsAttentionCount: number;
39
+
40
+ // Agent summaries
41
+ agentSummariesMap: Map<string, AgentSummary>;
42
+
43
+ // Projects
44
+ projects: Project[];
45
+ mergedProjects: Project[];
46
+ currentProject: string | undefined;
47
+ setCurrentProject: React.Dispatch<React.SetStateAction<string | undefined>>;
48
+ bridgeAgents: Agent[];
49
+ projectAgents: Agent[];
50
+ localAgentsForSidebar: Agent[];
51
+ recentRepos: ReturnType<typeof useRecentRepos>['recentRepos'];
52
+ addRecentRepo: ReturnType<typeof useRecentRepos>['addRecentRepo'];
53
+ getRecentProjects: ReturnType<typeof useRecentRepos>['getRecentProjects'];
54
+ workspaceRepos: ReturnType<typeof useWorkspaceRepos>['repos'];
55
+ refetchWorkspaceRepos: ReturnType<typeof useWorkspaceRepos>['refetch'];
56
+
57
+ // Spawn / Release
58
+ handleSpawn: (config: SpawnConfig) => Promise<boolean>;
59
+ handleReleaseAgent: (agent: Agent) => Promise<void>;
60
+ isSpawnModalOpen: boolean;
61
+ setIsSpawnModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
62
+ isSpawning: boolean;
63
+ spawnError: string | null;
64
+ setSpawnError: React.Dispatch<React.SetStateAction<string | null>>;
65
+
66
+ // Fleet
67
+ isFleetAvailable: boolean;
68
+ isFleetViewActive: boolean;
69
+ setIsFleetViewActive: React.Dispatch<React.SetStateAction<boolean>>;
70
+ fleetServers: ServerInfo[];
71
+ selectedServerId: string | undefined;
72
+ setSelectedServerId: React.Dispatch<React.SetStateAction<string | undefined>>;
73
+ handleServerReconnect: (serverId: string) => Promise<void>;
74
+
75
+ // Decisions
76
+ isDecisionQueueOpen: boolean;
77
+ setIsDecisionQueueOpen: React.Dispatch<React.SetStateAction<boolean>>;
78
+ decisions: Decision[];
79
+ decisionProcessing: Record<string, boolean>;
80
+ handleDecisionApprove: (id: string, optionId?: string) => Promise<void>;
81
+ handleDecisionReject: (id: string, reason?: string) => Promise<void>;
82
+ handleDecisionDismiss: (id: string) => Promise<void>;
83
+
84
+ // Tasks
85
+ handleTaskCreate: (task: TaskCreateRequest) => Promise<void>;
86
+ isCreatingTask: boolean;
87
+
88
+ // Activity
89
+ activityEvents: ActivityEvent[];
90
+ addActivityEvent: (event: Omit<ActivityEvent, 'id' | 'timestamp'>) => void;
91
+
92
+ // Log viewer
93
+ logViewerAgent: Agent | null;
94
+ setLogViewerAgent: React.Dispatch<React.SetStateAction<Agent | null>>;
95
+
96
+ // Agent profile
97
+ selectedAgentProfile: Agent | null;
98
+ setSelectedAgentProfile: React.Dispatch<React.SetStateAction<Agent | null>>;
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Context
103
+ // ---------------------------------------------------------------------------
104
+
105
+ const AgentContext = createContext<AgentContextValue | null>(null);
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Provider
109
+ // ---------------------------------------------------------------------------
110
+
111
+ export interface AgentProviderProps {
112
+ children: React.ReactNode;
113
+ /** WebSocket dashboard data (agents, messages, etc.) */
114
+ data: DashboardData | null;
115
+ /** Whether the WebSocket is connected */
116
+ isConnected: boolean;
117
+ }
118
+
119
+ /** Bridge-level agent names shown separately from project agents */
120
+ const BRIDGE_AGENT_NAMES = ['architect'];
121
+
122
+ export function AgentProvider({ children, data, isConnected }: AgentProviderProps) {
123
+ const {
124
+ cloudUser,
125
+ effectiveActiveWorkspaceId,
126
+ isWorkspaceFeaturesEnabled,
127
+ orchestratorWorkspaces,
128
+ orchestratorAgents,
129
+ activeWorkspaceId,
130
+ orchestratorSpawnAgent,
131
+ orchestratorStopAgent,
132
+ localAgents,
133
+ activeCloudWorkspaceId,
134
+ features,
135
+ apiAdapter,
136
+ setCloudWorkspaces,
137
+ } = useCloudWorkspace();
138
+
139
+ const hasWorkspaceApi = features.workspaces && !!apiAdapter;
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Agent merging
143
+ // ---------------------------------------------------------------------------
144
+
145
+ const combinedAgents = useMemo(() => {
146
+ return mergeAgentsForDashboard({
147
+ agents: data?.agents,
148
+ users: data?.users,
149
+ localAgents,
150
+ });
151
+ }, [data?.agents, data?.users, localAgents]);
152
+
153
+ const {
154
+ agents,
155
+ groups,
156
+ selectedAgent,
157
+ selectAgent,
158
+ searchQuery,
159
+ setSearchQuery,
160
+ totalCount,
161
+ onlineCount,
162
+ needsAttentionCount,
163
+ } = useAgentsHook({ agents: combinedAgents });
164
+
165
+ // Agent summaries
166
+ const agentSummariesMap = useMemo(() => {
167
+ const map = new Map<string, AgentSummary>();
168
+ for (const summary of data?.summaries ?? []) {
169
+ map.set(summary.agentName.toLowerCase(), summary);
170
+ }
171
+ return map;
172
+ }, [data?.summaries]);
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Activity feed
176
+ // ---------------------------------------------------------------------------
177
+
178
+ const [activityEvents, setActivityEvents] = useState<ActivityEvent[]>([]);
179
+
180
+ const addActivityEvent = useCallback((event: Omit<ActivityEvent, 'id' | 'timestamp'>) => {
181
+ const newEvent: ActivityEvent = {
182
+ ...event,
183
+ id: `activity-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
184
+ timestamp: new Date().toISOString(),
185
+ };
186
+ setActivityEvents(prev => [newEvent, ...prev].slice(0, 200));
187
+ }, []);
188
+
189
+ // Track previous agents to detect spawns/releases
190
+ const prevAgentsRef = useRef<Map<string, Agent>>(new Map());
191
+
192
+ useEffect(() => {
193
+ if (!combinedAgents || combinedAgents.length === 0) return;
194
+
195
+ const currentAgentMap = new Map(combinedAgents.map(a => [a.name, a]));
196
+ const prevAgentMap = prevAgentsRef.current;
197
+
198
+ if (prevAgentMap.size > 0) {
199
+ for (const [name, agent] of currentAgentMap) {
200
+ if (!prevAgentMap.has(name)) {
201
+ addActivityEvent({
202
+ type: 'agent_spawned',
203
+ actor: name,
204
+ actorType: 'agent',
205
+ title: 'came online',
206
+ description: agent.currentTask,
207
+ metadata: { cli: agent.cli, task: agent.currentTask },
208
+ });
209
+ } else {
210
+ const prevAgent = prevAgentMap.get(name)!;
211
+ if (prevAgent.status !== agent.status) {
212
+ if (agent.status === 'online' || agent.status === 'busy') {
213
+ addActivityEvent({
214
+ type: 'agent_online',
215
+ actor: name,
216
+ actorType: 'agent',
217
+ title: 'came online',
218
+ metadata: { cli: agent.cli },
219
+ });
220
+ } else if (agent.status === 'offline') {
221
+ addActivityEvent({
222
+ type: 'agent_offline',
223
+ actor: name,
224
+ actorType: 'agent',
225
+ title: 'went offline',
226
+ });
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ for (const [name] of prevAgentMap) {
233
+ if (!currentAgentMap.has(name)) {
234
+ addActivityEvent({
235
+ type: 'agent_released',
236
+ actor: name,
237
+ actorType: 'agent',
238
+ title: 'went offline',
239
+ });
240
+ }
241
+ }
242
+ }
243
+
244
+ prevAgentsRef.current = currentAgentMap;
245
+ }, [combinedAgents, addActivityEvent]);
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Projects
249
+ // ---------------------------------------------------------------------------
250
+
251
+ const [projects, setProjects] = useState<Project[]>([]);
252
+ const [currentProject, setCurrentProject] = useState<string | undefined>();
253
+
254
+ const { recentRepos, addRecentRepo, getRecentProjects } = useRecentRepos();
255
+ const { repos: workspaceRepos, refetch: refetchWorkspaceRepos } = useWorkspaceRepos({
256
+ workspaceId: effectiveActiveWorkspaceId ?? undefined,
257
+ apiBaseUrl: '/api',
258
+ enabled: isWorkspaceFeaturesEnabled && !!effectiveActiveWorkspaceId,
259
+ });
260
+
261
+ // Separate bridge-level agents from regular project agents
262
+ const { bridgeAgents, projectAgents } = useMemo(() => {
263
+ const bridge: Agent[] = [];
264
+ const project: Agent[] = [];
265
+
266
+ for (const agent of agents) {
267
+ if (agent.isHuman || agent.cli === 'dashboard') continue;
268
+ if (BRIDGE_AGENT_NAMES.includes(agent.name.toLowerCase())) {
269
+ bridge.push(agent);
270
+ } else {
271
+ project.push(agent);
272
+ }
273
+ }
274
+
275
+ return { bridgeAgents: bridge, projectAgents: project };
276
+ }, [agents]);
277
+
278
+ // Convert workspaces to projects
279
+ const hasWorkspaces = (isWorkspaceFeaturesEnabled
280
+ ? useCloudWorkspace().effectiveWorkspaces
281
+ : orchestratorWorkspaces
282
+ ).length > 0;
283
+ const effectiveWorkspaces = useCloudWorkspace().effectiveWorkspaces;
284
+
285
+ const bridgeBootstrapFetchedRef = useRef(false);
286
+ useEffect(() => {
287
+ if (hasWorkspaces) {
288
+ bridgeBootstrapFetchedRef.current = false;
289
+ if (workspaceRepos.length > 1 && effectiveActiveWorkspaceId) {
290
+ const repoProjects: Project[] = workspaceRepos.map((repo) => {
291
+ const repoName = repo.githubFullName.split('/').pop() || repo.githubFullName;
292
+ return {
293
+ id: repo.id,
294
+ path: repo.githubFullName,
295
+ name: repoName,
296
+ agents: [] as Agent[],
297
+ lead: undefined,
298
+ };
299
+ });
300
+ setProjects(repoProjects);
301
+ if (!currentProject || !repoProjects.find(p => p.id === currentProject)) {
302
+ setCurrentProject(repoProjects[0]?.id);
303
+ }
304
+ } else if (orchestratorWorkspaces.length > 0) {
305
+ const projectList: Project[] = orchestratorWorkspaces.map((workspace) => ({
306
+ id: workspace.id,
307
+ path: workspace.path,
308
+ name: workspace.name,
309
+ agents: orchestratorAgents
310
+ .filter((a) => a.workspaceId === workspace.id)
311
+ .map((a) => ({
312
+ name: a.name,
313
+ status: a.status === 'running' ? 'online' : 'offline',
314
+ isSpawned: true,
315
+ cli: a.provider,
316
+ cwd: a.cwd,
317
+ })) as Agent[],
318
+ lead: undefined,
319
+ }));
320
+ setProjects(projectList);
321
+ setCurrentProject(activeWorkspaceId);
322
+ } else if (isWorkspaceFeaturesEnabled && effectiveActiveWorkspaceId) {
323
+ const activeWs = effectiveWorkspaces.find(w => w.id === effectiveActiveWorkspaceId);
324
+ if (activeWs) {
325
+ const projectList: Project[] = [{
326
+ id: activeWs.id,
327
+ path: activeWs.path,
328
+ name: activeWs.name,
329
+ agents: [] as Agent[],
330
+ lead: undefined,
331
+ }];
332
+ setProjects(projectList);
333
+ setCurrentProject(activeWs.id);
334
+ }
335
+ }
336
+ }
337
+ }, [hasWorkspaces, orchestratorWorkspaces, orchestratorAgents, activeWorkspaceId, workspaceRepos, effectiveActiveWorkspaceId, currentProject, isWorkspaceFeaturesEnabled, effectiveWorkspaces]);
338
+
339
+ // Fetch bridge/project data for multi-project mode
340
+ useEffect(() => {
341
+ if (hasWorkspaces) return;
342
+
343
+ let cancelled = false;
344
+ const fetchProjects = async () => {
345
+ const result = await api.getBridgeData();
346
+ if (cancelled) return;
347
+
348
+ if (result.success && result.data) {
349
+ const bridgeData = result.data as {
350
+ projects?: Array<{
351
+ id: string;
352
+ name?: string;
353
+ path: string;
354
+ connected?: boolean;
355
+ agents?: Array<{ name: string; status: string; task?: string; cli?: string; model?: string; cwd?: string }>;
356
+ lead?: { name: string; connected: boolean };
357
+ }>;
358
+ connected?: boolean;
359
+ currentProjectPath?: string;
360
+ };
361
+
362
+ if (bridgeData.projects && bridgeData.projects.length > 0) {
363
+ const projectList: Project[] = bridgeData.projects.map((p) => ({
364
+ id: p.id,
365
+ path: p.path,
366
+ name: p.name || p.path.split('/').pop(),
367
+ agents: (p.agents || [])
368
+ .filter((a) => a.cli !== 'dashboard')
369
+ .map((a) => ({
370
+ name: a.name,
371
+ status: a.status === 'online' || a.status === 'active' ? 'online' : 'offline',
372
+ currentTask: a.task,
373
+ cli: a.cli,
374
+ model: a.model,
375
+ cwd: a.cwd,
376
+ })) as Agent[],
377
+ lead: p.lead,
378
+ }));
379
+ setProjects(projectList);
380
+ setCurrentProject((previous) => previous || projectList[0]?.id || previous);
381
+ }
382
+ }
383
+ };
384
+
385
+ if (!bridgeBootstrapFetchedRef.current) {
386
+ bridgeBootstrapFetchedRef.current = true;
387
+ void fetchProjects();
388
+ }
389
+
390
+ if (isConnected) {
391
+ return () => { cancelled = true; };
392
+ }
393
+
394
+ void fetchProjects();
395
+ const interval = setInterval(fetchProjects, 5000);
396
+ return () => {
397
+ cancelled = true;
398
+ clearInterval(interval);
399
+ };
400
+ }, [hasWorkspaces, isConnected]);
401
+
402
+ // Merge local broker agents into projects
403
+ const mergedProjects = useMemo(() => {
404
+ if (projects.length === 0) return projects;
405
+
406
+ if (workspaceRepos.length > 1) {
407
+ const allAgents: Agent[] = [...projectAgents];
408
+ const seenNames = new Set(projectAgents.map(a => a.name.toLowerCase()));
409
+ for (const oa of orchestratorAgents) {
410
+ if (!seenNames.has(oa.name.toLowerCase())) {
411
+ seenNames.add(oa.name.toLowerCase());
412
+ allAgents.push({
413
+ name: oa.name,
414
+ status: oa.status === 'running' ? 'online' : 'offline',
415
+ isSpawned: true,
416
+ cli: oa.provider,
417
+ cwd: oa.cwd,
418
+ } as Agent);
419
+ }
420
+ }
421
+
422
+ if (allAgents.length === 0) return projects;
423
+
424
+ const repoNames = new Set(projects.map(p => p.name));
425
+ const repoProjects = projects.map((project) => {
426
+ const repoName = project.name;
427
+ const matchingAgents = allAgents.filter((a) => a.cwd === repoName);
428
+ return { ...project, agents: [...project.agents, ...matchingAgents] };
429
+ });
430
+
431
+ const placedAgentNames = new Set(repoProjects.flatMap(p => p.agents.map(a => a.name.toLowerCase())));
432
+ const workspaceAgents = allAgents.filter((a) => {
433
+ if (placedAgentNames.has(a.name.toLowerCase())) return false;
434
+ return !a.cwd || !repoNames.has(a.cwd);
435
+ });
436
+
437
+ if (workspaceAgents.length > 0) {
438
+ const workspaceProject: Project = {
439
+ id: '__workspace__',
440
+ path: '/workspace',
441
+ name: 'Workspace',
442
+ agents: workspaceAgents,
443
+ };
444
+ return [workspaceProject, ...repoProjects];
445
+ }
446
+
447
+ return repoProjects;
448
+ }
449
+
450
+ if (projectAgents.length === 0) return projects;
451
+
452
+ return projects.map((project, index) => {
453
+ const isCurrentBrokerProject = index === 0 || project.id === currentProject;
454
+ if (isCurrentBrokerProject) {
455
+ const existingNames = new Set(project.agents.map((a) => a.name.toLowerCase()));
456
+ const newAgents = projectAgents.filter((a) => !existingNames.has(a.name.toLowerCase()));
457
+ return { ...project, agents: [...project.agents, ...newAgents] };
458
+ }
459
+ return project;
460
+ });
461
+ }, [projects, projectAgents, orchestratorAgents, currentProject, workspaceRepos.length]);
462
+
463
+ // Local agents for sidebar
464
+ const localAgentsForSidebar = useMemo(() => {
465
+ const humanUsers = agents.filter(a => a.isHuman);
466
+ if (mergedProjects.length > 0) return humanUsers;
467
+ const aiAgents = projectAgents.filter(a => !a.isHuman);
468
+ return [...aiAgents, ...humanUsers];
469
+ }, [mergedProjects, projectAgents, agents]);
470
+
471
+ // ---------------------------------------------------------------------------
472
+ // Spawn / Release
473
+ // ---------------------------------------------------------------------------
474
+
475
+ const [isSpawnModalOpen, setIsSpawnModalOpen] = useState(() => {
476
+ if (typeof window !== 'undefined') {
477
+ const params = new URLSearchParams(window.location.search);
478
+ if (params.get('spawn') === 'true') {
479
+ params.delete('spawn');
480
+ const newUrl = params.toString()
481
+ ? `${window.location.pathname}?${params.toString()}`
482
+ : window.location.pathname;
483
+ window.history.replaceState({}, '', newUrl);
484
+ return true;
485
+ }
486
+ }
487
+ return false;
488
+ });
489
+ const [isSpawning, setIsSpawning] = useState(false);
490
+ const [spawnError, setSpawnError] = useState<string | null>(null);
491
+
492
+ const handleSpawn = useCallback(async (config: SpawnConfig): Promise<boolean> => {
493
+ setIsSpawning(true);
494
+ setSpawnError(null);
495
+ try {
496
+ const commandParts = config.command.trim().split(/\s+/);
497
+ const provider = commandParts[0];
498
+ const modelIdx = commandParts.indexOf('--model');
499
+ const model = modelIdx !== -1 && commandParts[modelIdx + 1] ? commandParts[modelIdx + 1] : undefined;
500
+
501
+ if (hasWorkspaceApi && apiAdapter && activeCloudWorkspaceId) {
502
+ const result = await apiAdapter.spawnAgent(activeCloudWorkspaceId, {
503
+ name: config.name,
504
+ provider,
505
+ model,
506
+ cwd: config.cwd,
507
+ });
508
+ if (!result.success) {
509
+ setSpawnError(result.error || 'Failed to spawn agent');
510
+ return false;
511
+ }
512
+ return true;
513
+ }
514
+
515
+ if (orchestratorWorkspaces.length > 0 && activeWorkspaceId) {
516
+ await orchestratorSpawnAgent(config.name, undefined, config.command, config.cwd);
517
+ return true;
518
+ }
519
+
520
+ const result = await api.spawnAgent({
521
+ name: config.name,
522
+ cli: config.command,
523
+ cwd: config.cwd,
524
+ team: config.team,
525
+ shadowMode: config.shadowMode,
526
+ shadowOf: config.shadowOf,
527
+ shadowAgent: config.shadowAgent,
528
+ shadowTriggers: config.shadowTriggers,
529
+ shadowSpeakOn: config.shadowSpeakOn,
530
+ continueFrom: config.continueFrom,
531
+ });
532
+ if (!result.success) {
533
+ setSpawnError(result.error || 'Failed to spawn agent');
534
+ return false;
535
+ }
536
+ return true;
537
+ } catch (err) {
538
+ setSpawnError(err instanceof Error ? err.message : 'Failed to spawn agent');
539
+ return false;
540
+ } finally {
541
+ setIsSpawning(false);
542
+ }
543
+ }, [hasWorkspaceApi, apiAdapter, activeCloudWorkspaceId, orchestratorWorkspaces.length, activeWorkspaceId, orchestratorSpawnAgent]);
544
+
545
+ const handleReleaseAgent = useCallback(async (agent: Agent) => {
546
+ if (!agent.isSpawned) return;
547
+
548
+ const confirmed = window.confirm(`Are you sure you want to release agent "${agent.name}"?`);
549
+ if (!confirmed) return;
550
+
551
+ try {
552
+ if (hasWorkspaceApi && apiAdapter && activeCloudWorkspaceId) {
553
+ await apiAdapter.stopAgent(activeCloudWorkspaceId, agent.name);
554
+ return;
555
+ }
556
+
557
+ if (orchestratorWorkspaces.length > 0 && activeWorkspaceId) {
558
+ await orchestratorStopAgent(agent.name);
559
+ return;
560
+ }
561
+
562
+ const result = await api.releaseAgent(agent.name);
563
+ if (!result.success) {
564
+ console.error('Failed to release agent:', result.error);
565
+ }
566
+ } catch (err) {
567
+ console.error('Failed to release agent:', err);
568
+ }
569
+ }, [hasWorkspaceApi, apiAdapter, activeCloudWorkspaceId, orchestratorWorkspaces.length, activeWorkspaceId, orchestratorStopAgent]);
570
+
571
+ // ---------------------------------------------------------------------------
572
+ // Fleet
573
+ // ---------------------------------------------------------------------------
574
+
575
+ const isFleetAvailable = Boolean(data?.fleet?.servers?.length) || orchestratorWorkspaces.length > 0;
576
+ const [isFleetViewActive, setIsFleetViewActive] = useState(false);
577
+ const [fleetServers, setFleetServers] = useState<ServerInfo[]>([]);
578
+ const [selectedServerId, setSelectedServerId] = useState<string | undefined>();
579
+
580
+ useEffect(() => {
581
+ if (!isFleetViewActive) return;
582
+
583
+ const fetchFleetServers = async () => {
584
+ const result = await api.getFleetServers();
585
+ if (result.success && result.data) {
586
+ const servers: ServerInfo[] = result.data.servers.map((s) => ({
587
+ id: s.id,
588
+ name: s.name,
589
+ url: s.id === 'local' ? window.location.origin : `http://${s.id}`,
590
+ status: s.status === 'healthy' ? 'online' : s.status === 'degraded' ? 'degraded' : 'offline',
591
+ agentCount: s.agents.length,
592
+ uptime: s.uptime,
593
+ lastSeen: s.lastHeartbeat,
594
+ }));
595
+ setFleetServers(servers);
596
+ }
597
+ };
598
+
599
+ fetchFleetServers();
600
+ const interval = setInterval(fetchFleetServers, 5000);
601
+ return () => clearInterval(interval);
602
+ }, [isFleetViewActive]);
603
+
604
+ const handleServerReconnect = useCallback(async (serverId: string) => {
605
+ if (hasWorkspaceApi && apiAdapter) {
606
+ try {
607
+ const result = await apiAdapter.restartWorkspace(serverId);
608
+ if (result.success) {
609
+ setFleetServers(prev => prev.map(s =>
610
+ s.id === serverId ? { ...s, status: 'connecting' as const } : s
611
+ ));
612
+ setTimeout(async () => {
613
+ try {
614
+ const workspacesResult = await apiAdapter.getWorkspaceSummary();
615
+ if (workspacesResult.success && workspacesResult.data.workspaces) {
616
+ setCloudWorkspaces(workspacesResult.data.workspaces);
617
+ }
618
+ } catch (err) {
619
+ console.error('Failed to refresh workspaces after reconnect:', err);
620
+ }
621
+ }, 2000);
622
+ } else {
623
+ console.error('Failed to restart workspace:', result.error);
624
+ }
625
+ } catch (err) {
626
+ console.error('Failed to reconnect to server:', err);
627
+ }
628
+ } else {
629
+ console.warn('Server reconnect not fully supported in orchestrator mode');
630
+ }
631
+ }, [hasWorkspaceApi, apiAdapter, setCloudWorkspaces]);
632
+
633
+ // ---------------------------------------------------------------------------
634
+ // Decisions
635
+ // ---------------------------------------------------------------------------
636
+
637
+ const [isDecisionQueueOpen, setIsDecisionQueueOpen] = useState(false);
638
+ const [decisions, setDecisions] = useState<Decision[]>([]);
639
+ const [decisionProcessing, setDecisionProcessing] = useState<Record<string, boolean>>({});
640
+
641
+ useEffect(() => {
642
+ if (!isDecisionQueueOpen) return;
643
+
644
+ const fetchDecisions = async () => {
645
+ const result = await api.getDecisions();
646
+ if (result.success && result.data) {
647
+ setDecisions(result.data.decisions.map(convertApiDecision));
648
+ }
649
+ };
650
+
651
+ fetchDecisions();
652
+ const interval = setInterval(fetchDecisions, 5000);
653
+ return () => clearInterval(interval);
654
+ }, [isDecisionQueueOpen]);
655
+
656
+ const handleDecisionApprove = useCallback(async (decisionId: string, optionId?: string) => {
657
+ setDecisionProcessing((prev) => ({ ...prev, [decisionId]: true }));
658
+ try {
659
+ const result = await api.approveDecision(decisionId, optionId);
660
+ if (result.success) {
661
+ setDecisions((prev) => prev.filter((d) => d.id !== decisionId));
662
+ } else {
663
+ console.error('Failed to approve decision:', result.error);
664
+ }
665
+ } catch (err) {
666
+ console.error('Failed to approve decision:', err);
667
+ } finally {
668
+ setDecisionProcessing((prev) => ({ ...prev, [decisionId]: false }));
669
+ }
670
+ }, []);
671
+
672
+ const handleDecisionReject = useCallback(async (decisionId: string, reason?: string) => {
673
+ setDecisionProcessing((prev) => ({ ...prev, [decisionId]: true }));
674
+ try {
675
+ const result = await api.rejectDecision(decisionId, reason);
676
+ if (result.success) {
677
+ setDecisions((prev) => prev.filter((d) => d.id !== decisionId));
678
+ } else {
679
+ console.error('Failed to reject decision:', result.error);
680
+ }
681
+ } catch (err) {
682
+ console.error('Failed to reject decision:', err);
683
+ } finally {
684
+ setDecisionProcessing((prev) => ({ ...prev, [decisionId]: false }));
685
+ }
686
+ }, []);
687
+
688
+ const handleDecisionDismiss = useCallback(async (decisionId: string) => {
689
+ const result = await api.dismissDecision(decisionId);
690
+ if (result.success) {
691
+ setDecisions((prev) => prev.filter((d) => d.id !== decisionId));
692
+ }
693
+ }, []);
694
+
695
+ // ---------------------------------------------------------------------------
696
+ // Tasks
697
+ // ---------------------------------------------------------------------------
698
+
699
+ const [isCreatingTask, setIsCreatingTask] = useState(false);
700
+
701
+ const handleTaskCreate = useCallback(async (task: TaskCreateRequest) => {
702
+ setIsCreatingTask(true);
703
+ try {
704
+ const beadsPriority = PRIORITY_CONFIG[task.priority].beadsPriority;
705
+
706
+ const result = await api.createBead({
707
+ title: task.title,
708
+ assignee: task.agentName,
709
+ priority: beadsPriority,
710
+ type: 'task',
711
+ });
712
+
713
+ if (result.success && result.data?.bead) {
714
+ await api.sendRelayMessage({
715
+ to: task.agentName,
716
+ content: `New task assigned: "${task.title}" (P${beadsPriority})\nCheck \`bd ready\` for details.`,
717
+ });
718
+ console.log('Task created:', result.data.bead.id);
719
+ } else {
720
+ console.error('Failed to create task bead:', result.error);
721
+ throw new Error(result.error || 'Failed to create task');
722
+ }
723
+ } catch (err) {
724
+ console.error('Failed to create task:', err);
725
+ throw err;
726
+ } finally {
727
+ setIsCreatingTask(false);
728
+ }
729
+ }, []);
730
+
731
+ // ---------------------------------------------------------------------------
732
+ // Log viewer & profile
733
+ // ---------------------------------------------------------------------------
734
+
735
+ const [logViewerAgent, setLogViewerAgent] = useState<Agent | null>(null);
736
+ const [selectedAgentProfile, setSelectedAgentProfile] = useState<Agent | null>(null);
737
+
738
+ // ---------------------------------------------------------------------------
739
+ // Context value
740
+ // ---------------------------------------------------------------------------
741
+
742
+ const value = useMemo<AgentContextValue>(() => ({
743
+ agents, combinedAgents, groups, selectedAgent, selectAgent,
744
+ searchQuery, setSearchQuery, totalCount, onlineCount, needsAttentionCount,
745
+ agentSummariesMap,
746
+ projects, mergedProjects, currentProject, setCurrentProject,
747
+ bridgeAgents, projectAgents, localAgentsForSidebar,
748
+ recentRepos, addRecentRepo, getRecentProjects,
749
+ workspaceRepos, refetchWorkspaceRepos,
750
+ handleSpawn, handleReleaseAgent,
751
+ isSpawnModalOpen, setIsSpawnModalOpen, isSpawning, spawnError, setSpawnError,
752
+ isFleetAvailable, isFleetViewActive, setIsFleetViewActive,
753
+ fleetServers, selectedServerId, setSelectedServerId, handleServerReconnect,
754
+ isDecisionQueueOpen, setIsDecisionQueueOpen,
755
+ decisions, decisionProcessing,
756
+ handleDecisionApprove, handleDecisionReject, handleDecisionDismiss,
757
+ handleTaskCreate, isCreatingTask,
758
+ activityEvents, addActivityEvent,
759
+ logViewerAgent, setLogViewerAgent,
760
+ selectedAgentProfile, setSelectedAgentProfile,
761
+ }), [
762
+ agents, combinedAgents, groups, selectedAgent, selectAgent,
763
+ searchQuery, setSearchQuery, totalCount, onlineCount, needsAttentionCount,
764
+ agentSummariesMap,
765
+ projects, mergedProjects, currentProject,
766
+ bridgeAgents, projectAgents, localAgentsForSidebar,
767
+ recentRepos, addRecentRepo, getRecentProjects,
768
+ workspaceRepos, refetchWorkspaceRepos,
769
+ handleSpawn, handleReleaseAgent,
770
+ isSpawnModalOpen, isSpawning, spawnError,
771
+ isFleetAvailable, isFleetViewActive,
772
+ fleetServers, selectedServerId, handleServerReconnect,
773
+ isDecisionQueueOpen,
774
+ decisions, decisionProcessing,
775
+ handleDecisionApprove, handleDecisionReject, handleDecisionDismiss,
776
+ handleTaskCreate, isCreatingTask,
777
+ activityEvents, addActivityEvent,
778
+ logViewerAgent,
779
+ selectedAgentProfile,
780
+ ]);
781
+
782
+ return (
783
+ <AgentContext.Provider value={value}>
784
+ {children}
785
+ </AgentContext.Provider>
786
+ );
787
+ }
788
+
789
+ // ---------------------------------------------------------------------------
790
+ // Hook
791
+ // ---------------------------------------------------------------------------
792
+
793
+ export function useAgentContext(): AgentContextValue {
794
+ const ctx = useContext(AgentContext);
795
+ if (!ctx) {
796
+ throw new Error('useAgentContext must be used within an AgentProvider');
797
+ }
798
+ return ctx;
799
+ }