Onboarding updates - #55
Conversation
Add continueFrom support to SpawnModal UI with toggle switch. Wire continueFrom through App.tsx to the spawn API call. Add tests for toggle enabled/disabled states. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove hardcoded model options from SpawnModal.tsx - Import CLAUDE_MODEL_OPTIONS, CURSOR_MODEL_OPTIONS, etc. from agent-relay/broker - Use DefaultModels for fallback values - SettingsPage.tsx also imports from SDK Source of truth is now packages/shared/cli-registry.yaml in relay repo. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…endencies Remove all direct cloud API dependencies from the dashboard package, making it a standalone UI kit that receives configuration via adapters. - Delete cloud-specific components: CloudSessionProvider, BillingResult, SessionExpiredModal, WorkspaceStatusIndicator - Delete cloud hooks: useSession, useWorkspaceMembers, useWorkspaceStatus - Delete settings panels: BillingSettingsPanel, TeamSettingsPanel, WorkspaceSettingsPanel, CredentialAssignmentSection - Delete monolithic server.ts (6.5k lines), replace with proxy-server - Add adapters layer (DashboardConfigProvider, types) for host integration - Simplify App.tsx, SettingsPage, Header, Sidebar to use adapter pattern - Remove cloudApi.ts dependency from lib/ Net: -12,652 lines removed, +2,507 lines added Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In proxy mode, the dashboard now connects to the broker's /ws endpoint for real-time event streaming instead of polling Relaycast REST API every 3 seconds. Falls back to snapshot polling if broker WS is down. - Add handleHybridWebSocket with exponential backoff reconnect - Route proxy mode WS connections through hybrid handler - Add BrokerEvent type and applyBrokerEvent state patching - Handle relay_inbound, agent lifecycle, and worker status events Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract business logic from the 3,515-line App.tsx into four dedicated context providers, reducing it to a ~1,230-line layout shell. - SettingsProvider: theme, display, notification preferences (247 lines) - CloudWorkspaceProvider: workspace list/selection, cloud user state (402 lines) - AgentProvider: agent merging, spawn/release, fleet, decisions (799 lines) - MessageProvider: channels, threads, DM, send ops, presence (1,208 lines) Each provider uses React.createContext + useContext with proper TypeScript types. App.tsx now contains only the provider tree and layout JSX. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- metrics.ts → fetchCloudMetrics() proxy - needs-attention.ts → fetchCloudNeedsAttention() proxy - health-worker-manager.ts → fetchBrokerHealth() proxy - spawned-agents.ts → fetchBrokerSpawnedAgents() proxy - Added proxy-route-table.ts with explicit route mapping - Updated server.ts to consume proxy helpers
- Refactored relaycast-provider.ts from 832 to 366 lines - Removed custom workspaceGet transport, manual envelope parsing - Removed DM participant extraction, local cache invalidation - Added relaycast-provider-helpers.ts and relaycast-provider-types.ts - Uses SDK clients for all reads + shared contracts isBrokerIdentity()
# Conflicts: # packages/dashboard-server/src/lib/spawned-agents.ts # packages/dashboard-server/src/server.ts # packages/dashboard-server/src/services/index.ts
…for spawned agents Reorder hybrid WS to send the initial snapshot before connecting the broker stream so the client always has non-null data. Dual-send messages to spawned agents via both Relaycast (observer visibility) and broker /api/send (reliable delivery). Handle raw broker events on the client and bootstrap empty state so events before the snapshot aren't lost. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| app.get('/api/relay-config', (_req: Request, res: Response) => { | ||
| const config = ctx.resolveRelaycastConfig(); | ||
| if (!config) { | ||
| res.status(503).json({ | ||
| success: false, | ||
| error: `Relaycast credentials not found in ${path.join(ctx.dataDir, 'relaycast.json')}`, | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| if (!config.agentToken) { | ||
| res.status(503).json({ | ||
| success: false, | ||
| error: 'Relaycast agent token is missing from relaycast.json', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| res.json({ | ||
| success: true, | ||
| baseUrl: config.baseUrl, | ||
| apiKey: config.apiKey, | ||
| agentToken: config.agentToken, | ||
| agentName: config.agentName ?? path.basename(path.resolve(ctx.dataDir, '..')), | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🔴 Relaycast API key exposed to browser clients via unauthenticated /api/relay-config endpoint
The /api/relay-config endpoint at packages/dashboard-server/src/routes/relay-config.ts:24-31 returns the Relaycast apiKey and agentToken directly to any HTTP client without authentication. The frontend RelayConfigProvider.tsx fetches this endpoint to initialize the @relaycast/react SDK in the browser.
Security Impact
The apiKey is the workspace-level Relaycast API key (rk_...), and agentToken is the agent-scoped token. Both are returned in plaintext JSON to any caller. While the dashboard is typically served on localhost, in cloud deployments or when exposed on a network, any client that can reach the dashboard server can obtain these credentials and use them to:
- Read all messages in the Relaycast workspace
- Send messages as the dashboard agent
- Manage channels
The endpoint has no authentication middleware — no session check, no workspace token validation, no CSRF protection. This contrasts with the auth routes which use validateWorkspaceToken middleware.
The .claude/rules/security-auth-middleware.md rule explicitly states: "All mutation endpoints that modify credentials, tokens, or sensitive configuration must be covered by workspace-token validation middleware." While this is a GET endpoint, it exposes sensitive tokens.
Prompt for agents
In packages/dashboard-server/src/routes/relay-config.ts, add authentication or access control to the GET /api/relay-config endpoint. At minimum, when WORKSPACE_TOKEN is set (cloud deployments), require a valid session cookie or workspace token before returning the apiKey and agentToken. For local-only deployments (no WORKSPACE_TOKEN), the current behavior may be acceptable since the server is on localhost. Consider adding a check like: if (process.env.WORKSPACE_TOKEN && !isValidSession(req)) return res.status(401).json({ error: 'Unauthorized' }).
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Also includes #54 |
| }); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
this was in this file but we should support interrupt
// Agent interrupt — broker does not have this endpoint yet.
app.post('/api/agents/by-name/:name/interrupt', (req: Request, res: Response) => {
const name = typeof req.params.name === 'string' ? decodeURIComponent(req.params.name) : '';
res.status(501).json({
success: false,
error: 'Agent interrupt is not yet supported by the broker HTTP API.',
name,
});
});
| if (result.success) { | ||
| // Success! The optimistic message will be cleaned up when | ||
| // the real message arrives via WebSocket | ||
| // If the server returned a canonical message ID, update the optimistic | ||
| // message so dedup logic can match it when the real event arrives. | ||
| const canonicalId = result.data?.messageId; | ||
| if (canonicalId) { | ||
| setOptimisticMessages((prev) => | ||
| prev.map((m) => (m.id === optimisticId ? { ...m, id: canonicalId } : m)), | ||
| ); | ||
| } |
There was a problem hiding this comment.
🟡 Optimistic messages with updated canonical IDs are never cleaned up, causing duplicates
When sendMessage succeeds and the server returns a messageId, the optimistic message's ID is updated to the canonical ID. However, the optimistic message is never removed from optimisticMessages state — it stays in the sending status forever. When the real message arrives via WebSocket with the same canonical ID, the dedup logic in mergedMessages should catch it, but the optimistic message remains with status: 'sending' while the real message has no status or status: 'read'.
Root Cause
At packages/dashboard/src/components/hooks/useMessages.ts:277-285, on successful send, the code updates the optimistic message's ID to the canonical one but never transitions its status from 'sending' to 'sent' or removes it. The comment at the old line 275-276 said "The optimistic message will be cleaned up when the real message arrives via WebSocket" — but after updating the ID to match the canonical one, the dedup logic in mergedMessages (which filters optimistic messages whose IDs match real messages) should work. However, the optimistic message retains status: 'sending' indefinitely, which means the UI shows a perpetual "sending" indicator on the message even after it was successfully delivered.
The test at useMessages.test.ts:36 confirms this: expect(result.current.messages[0]?.status).toBe('sending') — this is the expected behavior per the test, but it means the message never transitions to a delivered state.
Impact: Messages appear stuck in "sending" state in the UI until the WebSocket delivers the same message with the canonical ID, at which point dedup kicks in. If the WebSocket snapshot doesn't include the message (e.g., it was a DM not in the current view), the sending indicator persists forever.
Was this helpful? React with 👍 or 👎 to provide feedback.
Replace the inline /api/history/stats handler and remove the broken /api/history proxy (which returned HTML for unimplemented routes). New routes in history-relaycast.ts: - GET /api/history/stats — agent/message counts from Relaycast - GET /api/history/messages — filterable message history from all channels and DMs via Relaycast SDK - GET /api/history/conversations — channel + DM conversation list with participant info, message counts, and last message preview Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove file:// references to @agent-relay/contracts and @agent-relay/sdk - Inline isBrokerIdentity function since @agent-relay/contracts is not on npm - Update to latest npm versions: - @agent-relay/sdk: ^3.0.1 - @agent-relay/config: ^2.4.7 - @agent-relay/protocol: ^2.3.14 - @agent-relay/storage: ^2.3.14 - @agent-relay/trajectory: ^2.4.7 - @agent-relay/utils: ^2.4.7 - @relaycast/sdk: ^0.4.2 - @relaycast/types: ^0.4.2 - Update createRelaycastClient to use new RelayCast API - Fix TypeScript errors from SDK API changes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ndle The @agent-relay/config package includes Node.js dependencies (child_process, crypto, fs, os, path) that cannot be bundled for browser environments. - Inline RegistryModelOptions in SpawnModal.tsx instead of importing from @agent-relay/config - Remove transpilePackages for @agent-relay/config from next.config.js - Remove @agent-relay/config path alias from tsconfig.json The model options data was previously accessed via a path alias pointing to a local file. With npm packages, the full package gets imported including Node.js modules, causing webpack build failures. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add fallback implementation in relaycast-provider-helpers.ts for when SDK's createRelaycastClient is not exported (test compatibility) - Create simple fetch-based HTTP client for reader operations to avoid registration requirements - Handle both snake_case and camelCase response fields from SDK - Skip WORKSPACE_TOKEN auth check in standalone mode for /api/relay-config to allow proper 503 response when credentials are missing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add @testing-library/jest-dom for toHaveTextContent matcher - Create vitest.setup.ts to import jest-dom matchers - Update vitest.config.ts to use setup file - Add [broker] Run pattern to TELEMETRY_NOISE_PATTERN for noise filtering Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The dashboard server does not support --team-dir option. In mock mode, the test data directory is sufficient. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ints WebSocket endpoints like /ws require a protocol upgrade. Regular HTTP GET requests should return 426 to indicate the endpoint exists but requires WebSocket upgrade. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Large-scale refactor and feature update that restructures the dashboard into a clean, modular architecture while adding new onboarding and session management capabilities.
Architecture Refactors
Dashboard as pure UI kit: Removed all direct cloud API dependencies (billing, sessions, workspace status), making the dashboard a standalone UI that receives configuration via an adapter pattern (
DashboardConfigProvider)CloudSessionProvider,BillingResult,SessionExpiredModal,WorkspaceStatusIndicator,useSession,useWorkspaceMembers,useWorkspaceStatusSplit App.tsx into focused providers: Extracted the 3,500-line
App.tsxinto four dedicated context providers:SettingsProvider— theme, display, notification preferencesCloudWorkspaceProvider— workspace list/selection, cloud user stateAgentProvider— agent merging, spawn/release, fleet, decisionsMessageProvider— channels, threads, DM, send ops, presenceServer route extraction: Broke monolithic
server.tsinto modular route files underroutes/(agents, channels, data, health, reactions, broker-proxy) with a proxy route tableRelaycast provider migration: Refactored
relaycast-provider.tsfrom 832 → 366 lines by adopting SDK-only transport, removing custom envelope parsing and manual cache invalidationExtract business logic to proxy pass-throughs: Metrics, needs-attention, health, and spawned-agents endpoints now proxy to broker/cloud instead of containing business logic
New Features
Hybrid WebSocket mode: Dashboard connects to broker's
/wsendpoint for real-time event streaming instead of polling Relaycast REST API every 3 seconds. Falls back to snapshot polling if broker WS is unavailable. Includes exponential backoff reconnect.Resume Previous Session toggle: Added
continueFromsupport toSpawnModalUI so agents can resume prior sessionsCentralized model registry: Removed hardcoded model options from
SpawnModal— now importsCLAUDE_MODEL_OPTIONS,CURSOR_MODEL_OPTIONS, etc. fromagent-relay/brokerSDK (source of truth:cli-registry.yaml)Onboarding page overhaul: Significantly expanded the onboarding page (~870 lines added, 170 removed) with improved provider setup flows
Server Infrastructure
cli-auth.ts,file-search.ts,log-reader.ts,spawned-agents.ts,relay-client.ts(1,417 lines)logs.ts,mock.ts,proxy.ts,standalone.ts/api/send(reliable delivery)Tests
SpawnModaltoggle states,proxy-server,relaycast-provider,spawned-agents,file-search,log-reader,proxy-route-table,useWebSocket,useMessages,utils84 files changed across
packages/dashboard/andpackages/dashboard-server/