-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Add WebRTC signaling and peer connection support #285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughIntroduces WebRTC support to the Agentuity framework. Adds a new webrtc-test application demonstrating agent-based WebRTC video calling with React UI. Implements server-side signaling via WebRTCRoomManager, client-side connection management via WebRTCManager, and React integration via useWebRTCCall hook. Updates CLI to recognize webrtc() routing and reorders dev build metadata generation. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client (Browser)
participant React as React Component<br/>(useWebRTCCall)
participant WebRTCMgr as WebRTCManager<br/>(Frontend)
participant SignalWS as Signaling<br/>WebSocket
participant RoomMgr as WebRTCRoomManager<br/>(Server)
participant Peer as Remote Peer
Client->>React: click Join Call
React->>WebRTCMgr: connect()
WebRTCMgr->>WebRTCMgr: getUserMedia()
WebRTCMgr->>React: onLocalStream(stream)
React->>Client: render local video
WebRTCMgr->>SignalWS: open connection
SignalWS->>RoomMgr: ws open event
WebRTCMgr->>SignalWS: send join{roomId}
SignalWS->>RoomMgr: handleMessage(join)
RoomMgr->>RoomMgr: assign peerId, store room
RoomMgr->>SignalWS: send joined{peerId, peers}
SignalWS->>WebRTCMgr: recv joined
WebRTCMgr->>WebRTCMgr: createPeerConnection()
WebRTCMgr->>React: onStatusChange('signaling')
alt Remote Peer Already in Room
RoomMgr->>Peer: broadcast peer-joined{peerId}
Peer->>Peer: createOffer()
Peer->>SignalWS: send sdp{offer}
SignalWS->>RoomMgr: handleMessage(sdp)
RoomMgr->>SignalWS: relay to joining peer
SignalWS->>WebRTCMgr: recv sdp{offer}
WebRTCMgr->>WebRTCMgr: setRemoteDescription(offer)
WebRTCMgr->>WebRTCMgr: createAnswer()
WebRTCMgr->>SignalWS: send sdp{answer}
SignalWS->>RoomMgr: handleMessage(sdp)
RoomMgr->>SignalWS: relay to remote
SignalWS->>Peer: recv sdp{answer}
Peer->>Peer: setRemoteDescription(answer)
end
WebRTCMgr->>WebRTCMgr: ICE candidates
loop ICE Exchange
WebRTCMgr->>SignalWS: send ice{candidate}
SignalWS->>RoomMgr: handleMessage(ice)
RoomMgr->>SignalWS: relay candidate
SignalWS->>Peer: recv ice{candidate}
Peer->>Peer: addIceCandidate()
end
WebRTCMgr->>WebRTCMgr: ontrack event
WebRTCMgr->>React: onRemoteStream(stream)
React->>Client: render remote video
WebRTCMgr->>React: onStatusChange('connected')
Client->>Peer: Video/audio exchange over peer connection
Client->>React: click Hangup
React->>WebRTCMgr: hangup()
WebRTCMgr->>SignalWS: close connection
SignalWS->>RoomMgr: handleDisconnect(ws)
RoomMgr->>Peer: broadcast peer-left{peerId}
WebRTCMgr->>WebRTCMgr: close RTCPeerConnection
WebRTCMgr->>React: onStatusChange('disconnected')
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (22)
packages/cli/src/cmd/dev/index.ts (1)
363-423: Approve reordering logic; consider removing duplicate declaration.The reordered build flow correctly ensures metadata is generated before syncing with the backend. The sequence (discover agents/routes → generate metadata → write → sync) is logical and properly structured.
However, Line 387 redeclares
srcDirwhich is already available from Line 94. While not a syntax error due to scoping, this is unnecessary and could cause confusion.🔎 Suggested refactor to remove duplicate declaration
- const srcDir = join(rootDir, 'src'); const agents = await discoverAgents( srcDir, project?.projectId ?? '',apps/testing/webrtc-test/package.json (1)
25-25: Consider pinning@types/bunversion for reproducible builds.Using
"latest"for@types/bunmay lead to inconsistent type definitions across different environments or CI runs. Consider pinning to a specific version.apps/testing/webrtc-test/src/web/AGENTS.md (2)
14-23: Add language specifier to fenced code block.The directory structure code block is missing a language specifier, which affects rendering.
🔎 Suggested fix
-``` +```text src/web/ ├── App.tsx
198-203: Add language specifier to fenced code block.🔎 Suggested fix
-``` +```text src/web/public/packages/runtime/src/router.ts (1)
742-776: The webrtc() route doesn't currently require AgentContext capture.The
WebRTCRoomManagerclass doesn't use logging, context-dependent features, or any agent utilities—its methods only manage room state and relay WebSocket messages. However, since the regularwebsocket()implementation capturesAgentContextfor consistency and future flexibility, capturing context inwebrtc()would align with that pattern if the signaling logic ever needs to access logging or other context-dependent features.apps/testing/webrtc-test/src/api/AGENTS.md (1)
11-20: Consider adding a language identifier to the fenced code block.The directory structure code block on line 11 could specify a language (e.g.,
textorplaintext) for better syntax highlighting and tool compatibility.Proposed fix
-``` +```text src/api/ ├── index.ts (optional, mounted at /api)apps/testing/webrtc-test/README.md (1)
19-38: Consider adding a language identifier to the fenced code block.The directory structure code block on line 19 could specify a language (e.g.,
textorplaintext) for better syntax highlighting and tool compatibility.Proposed fix
-``` +```text my-app/ ├── src/apps/testing/webrtc-test/src/agent/AGENTS.md (2)
70-72: Minor: Usesubstringinstead of deprecatedsubstr.The example code uses
substrwhich is deprecated. Consider updating tosubstringfor consistency with modern JavaScript.🔎 Proposed fix
- agentId: `agent-${Math.random().toString(36).substr(2, 9)}`, + agentId: `agent-${Math.random().toString(36).substring(2, 11)}`,
12-21: Consider adding language specifier to directory structure code blocks.Static analysis flagged lines 12 and 195 for missing language specifiers on fenced code blocks. Adding
textorplaintextwould satisfy the linter.🔎 Proposed fix for line 12
-``` +```text src/agent/ ├── hello/apps/testing/webrtc-test/src/web/App.tsx (3)
28-36: useEffect dependency on refs has no practical effect.React refs (
localVideoRef,remoteVideoRef) are stable objects that don't change between renders. Including them in the dependency array won't cause the effect to re-run when streams are attached. However, the properties being set here (muted,playsInline) are already applied via JSX attributes on lines 85 and 89, making this effect redundant.Consider removing this effect or updating it to depend on actual stream state if additional setup is needed when streams change.
🔎 Proposed fix - remove redundant effect
- // Auto-attach streams to video elements when refs are ready - useEffect(() => { - if (localVideoRef.current) { - localVideoRef.current.muted = true; - localVideoRef.current.playsInline = true; - } - if (remoteVideoRef.current) { - remoteVideoRef.current.playsInline = true; - } - }, [localVideoRef, remoteVideoRef]); -
38-41: Consider validating roomId before joining.The
handleJoinfunction doesn't validate thatroomIdis non-empty before callingconnect(). An empty room ID could lead to unexpected behavior.🔎 Proposed fix
const handleJoin = () => { + if (!roomId.trim()) { + return; + } setJoined(true); connect(); };
68-70: Consider disabling the join button when roomId is empty.This would provide visual feedback to users and prevent accidental joins with empty room IDs.
🔎 Proposed fix
- <button className="join-btn" onClick={handleJoin}> + <button className="join-btn" onClick={handleJoin} disabled={!roomId.trim()}> Join Call </button>packages/react/src/webrtc.tsx (4)
108-119: Consider handling invalid base URLs gracefully.If
context?.baseUrlcontains an invalid URL that doesn't match the regex pattern,wsBasecould produce unexpected results. The current implementation assumes well-formed HTTP(S) URLs.
121-160: ESLint disable comment masks a potential stale closure issue.The
useMemocreates callbacks that capturelocalVideoRefandremoteVideoRefrefs, which is correct since refs are stable. However, disabling the exhaustive-deps rule entirely hides the actual issue: callback functions are recreated when options change, but the callbacks themselves reference refs which don't need to be dependencies.Consider being explicit about which dependencies are intentionally omitted:
- // eslint-disable-next-line - }, [signalUrl, options.roomId, options.polite, options.iceServers, options.media]); + // Refs are stable and don't need to be in deps + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [signalUrl, options.roomId, options.polite, options.iceServers, options.media]);
162-176: Manager recreation on options change may cause unintended disconnects.When
managerOptionschanges (e.g.,options.mediareference changes), the effect will dispose the old manager and create a new one, disconnecting the call. This could be surprising if a parent component re-renders with a new object reference formediaconstraints.Consider documenting this behavior or using
useRefto track if options meaningfully changed:🔎 Alternative approach using stable refs for options
// Store options in ref to compare for meaningful changes const optionsRef = useRef(options); const hasOptionsChanged = ( optionsRef.current.roomId !== options.roomId || optionsRef.current.signalUrl !== options.signalUrl ); useEffect(() => { optionsRef.current = options; }, [options]); useEffect(() => { // Only recreate if critical options changed // ... }, [hasOptionsChanged, ...]);
182-187: Redundant state updates inhangupcallback.The
WebRTCManager.hangup()method already triggerssetStatus('disconnected')via theonStatusChangecallback configured inmanagerOptions. Setting state again here creates duplicate renders.However, the explicit
setPeerId(null)andsetRemotePeerId(null)are needed since those aren't cleared by the manager's callback. Consider removing only the redundantsetStatus:const hangup = useCallback(() => { managerRef.current?.hangup(); - setStatus('disconnected'); setPeerId(null); setRemotePeerId(null); }, []);packages/runtime/src/webrtc-signaling.ts (3)
52-54: Peer ID generation could be more robust.The current implementation uses timestamp + counter, which works but could produce collisions if the server restarts within the same millisecond. For a signaling server, this is low-risk, but consider using a more robust ID generator.
🔎 Alternative using crypto
private generatePeerId(): string { - return `peer-${Date.now()}-${++this.peerIdCounter}`; + return `peer-${crypto.randomUUID()}`; }
74-101: Consider validatingroomIdinput.Empty or excessively long room IDs are accepted without validation. While the server injects peer IDs to prevent spoofing, malicious clients could create many rooms with arbitrary IDs.
🔎 Proposed validation
handleJoin(ws: WebSocketConnection, roomId: string): void { + if (!roomId || roomId.length > 128) { + this.send(ws, { t: 'error', message: 'Invalid room ID' }); + return; + } + let room = this.rooms.get(roomId); // ... }
131-155: Silent failure when targeting non-existent peer.When
tois specified but the target peer doesn't exist in the room, the message is silently dropped. This could make debugging connection issues harder.🔎 Consider sending error feedback
if (to) { // Send to specific peer const targetPeer = room.get(to); if (targetPeer) { this.send(targetPeer.ws, msg); + } else { + this.send(ws, { t: 'error', message: `Peer ${to} not found` }); } } else {packages/frontend/src/webrtc-manager.ts (3)
268-277: Non-null assertion onpeerIdcould throw if called before join.
this.peerId!assumes the peer ID is set, butonicecandidatecould theoretically fire before thejoinedmessage is processed if there's a race condition.🔎 Safer approach
this.pc.onicecandidate = (event) => { if (event.candidate) { + if (!this.peerId) return; // Not yet joined this.send({ t: 'ice', - from: this.peerId!, + from: this.peerId, to: this.remotePeerId ?? undefined, candidate: event.candidate.toJSON(), }); } };
297-303: Consider handling 'failed' ICE connection state.The handler responds to 'disconnected' and 'connected' states but doesn't handle 'failed', which indicates an unrecoverable ICE failure. Users should be notified of this condition.
🔎 Proposed enhancement
this.pc.oniceconnectionstatechange = () => { if (this.pc?.iceConnectionState === 'disconnected') { this.setStatus('signaling'); } else if (this.pc?.iceConnectionState === 'connected') { this.setStatus('connected'); + } else if (this.pc?.iceConnectionState === 'failed') { + this.callbacks.onError?.(new Error('ICE connection failed')); + this.closePeerConnection(); + this.setStatus('signaling'); } };
62-65: Reliance on Google STUN servers may have privacy/reliability implications.Public Google STUN servers are commonly used but may not be appropriate for all deployments. The PR objectives mention "TURN guidance" as a future improvement. Consider documenting this default behavior.
What are recommended public STUN servers for WebRTC applications?
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
apps/testing/integration-suite/src/generated/app.tsis excluded by!**/generated/**apps/testing/webrtc-test/src/generated/app.tsis excluded by!**/generated/**apps/testing/webrtc-test/src/web/public/favicon.icois excluded by!**/*.icobun.lockis excluded by!**/*.lock
📒 Files selected for processing (28)
apps/testing/webrtc-test/.gitignore(1 hunks)apps/testing/webrtc-test/.vscode/settings.json(1 hunks)apps/testing/webrtc-test/AGENTS.md(1 hunks)apps/testing/webrtc-test/README.md(1 hunks)apps/testing/webrtc-test/agentuity.config.ts(1 hunks)apps/testing/webrtc-test/agentuity.json(1 hunks)apps/testing/webrtc-test/app.ts(1 hunks)apps/testing/webrtc-test/package.json(1 hunks)apps/testing/webrtc-test/src/agent/AGENTS.md(1 hunks)apps/testing/webrtc-test/src/agent/hello/agent.ts(1 hunks)apps/testing/webrtc-test/src/agent/hello/index.ts(1 hunks)apps/testing/webrtc-test/src/api/AGENTS.md(1 hunks)apps/testing/webrtc-test/src/api/index.ts(1 hunks)apps/testing/webrtc-test/src/web/AGENTS.md(1 hunks)apps/testing/webrtc-test/src/web/App.tsx(1 hunks)apps/testing/webrtc-test/src/web/frontend.tsx(1 hunks)apps/testing/webrtc-test/src/web/index.html(1 hunks)apps/testing/webrtc-test/tsconfig.json(1 hunks)packages/cli/src/cmd/build/ast.ts(1 hunks)packages/cli/src/cmd/dev/index.ts(2 hunks)packages/frontend/src/index.ts(1 hunks)packages/frontend/src/webrtc-manager.ts(1 hunks)packages/react/src/index.ts(2 hunks)packages/react/src/webrtc.tsx(1 hunks)packages/runtime/src/index.ts(1 hunks)packages/runtime/src/router.ts(4 hunks)packages/runtime/src/webrtc-signaling.ts(1 hunks)packages/runtime/test/webrtc-signaling.test.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{js,ts,tsx,jsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,tsx,jsx,json}: Format code with Prettier using tabs (width 3), single quotes, semicolons, and 100 character line width
Use ESLint with TypeScript, React, and JSON support for linting
Files:
apps/testing/webrtc-test/src/agent/hello/agent.tsapps/testing/webrtc-test/src/agent/hello/index.tspackages/cli/src/cmd/build/ast.tsapps/testing/webrtc-test/agentuity.config.tspackages/runtime/src/index.tspackages/frontend/src/index.tsapps/testing/webrtc-test/src/web/App.tsxpackages/react/src/webrtc.tsxapps/testing/webrtc-test/package.jsonapps/testing/webrtc-test/src/web/frontend.tsxapps/testing/webrtc-test/agentuity.jsonapps/testing/webrtc-test/src/api/index.tsapps/testing/webrtc-test/app.tspackages/runtime/src/webrtc-signaling.tspackages/runtime/test/webrtc-signaling.test.tspackages/frontend/src/webrtc-manager.tspackages/react/src/index.tsapps/testing/webrtc-test/tsconfig.jsonpackages/runtime/src/router.tspackages/cli/src/cmd/dev/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Always use InferInput and InferOutput type helpers from @agentuity/core instead of StandardSchemaV1.InferOutput
Files:
apps/testing/webrtc-test/src/agent/hello/agent.tsapps/testing/webrtc-test/src/agent/hello/index.tspackages/cli/src/cmd/build/ast.tsapps/testing/webrtc-test/agentuity.config.tspackages/runtime/src/index.tspackages/frontend/src/index.tsapps/testing/webrtc-test/src/web/App.tsxpackages/react/src/webrtc.tsxapps/testing/webrtc-test/src/web/frontend.tsxapps/testing/webrtc-test/src/api/index.tsapps/testing/webrtc-test/app.tspackages/runtime/src/webrtc-signaling.tspackages/runtime/test/webrtc-signaling.test.tspackages/frontend/src/webrtc-manager.tspackages/react/src/index.tspackages/runtime/src/router.tspackages/cli/src/cmd/dev/index.ts
packages/cli/**/*.{ts,tsx}
📄 CodeRabbit inference engine (packages/cli/AGENTS.md)
packages/cli/**/*.{ts,tsx}: Use Bun provided APIs instead of legacy Node.js APIs (e.g.,Bun.file(f).exists()instead ofexistsSync(f))
Use async/await for all I/O operations
All code must be TypeScript with strict mode
Files:
packages/cli/src/cmd/build/ast.tspackages/cli/src/cmd/dev/index.ts
packages/cli/src/cmd/**/*.ts
📄 CodeRabbit inference engine (packages/cli/AGENTS.md)
packages/cli/src/cmd/**/*.ts: Always define interfaces for command options, never useanyfor type safety
Usetui.*helpers for formatted output without log prefixes (success, error, warning, info, bold, muted, link)
Usectx.loggerfor standard logging with prefixes (fatal, error, info, debug), uselogger.fatal()to log error and exit with code 1 instead oflogger.error()+process.exit(1)
Follow commander.js best practices for command parsing and routing
Subcommands are defined in separate files and exported asSubcommandDefinition
Commands must validate their own config requirements
Usetui.success(),tui.error(),tui.warning(),tui.info()for semantic messages in command output
Usetui.bold(),tui.muted(),tui.link()for text formatting in command output
Files:
packages/cli/src/cmd/build/ast.tspackages/cli/src/cmd/dev/index.ts
packages/*/src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Always use StructuredError from @agentuity/core for error classes with structured data
Files:
packages/cli/src/cmd/build/ast.tspackages/runtime/src/index.tspackages/frontend/src/index.tspackages/runtime/src/webrtc-signaling.tspackages/frontend/src/webrtc-manager.tspackages/react/src/index.tspackages/runtime/src/router.tspackages/cli/src/cmd/dev/index.ts
packages/runtime/src/**/*.ts
📄 CodeRabbit inference engine (packages/runtime/AGENTS.md)
packages/runtime/src/**/*.ts: Usectx.logger.info/warn/error()for logging instead of console.log
Agents can return ReadableStream for streaming responses via the stream() router method
Use Hono context-based API design patterns and middleware support in route handlers
All handlers should be async functions
Use method chaining for Hono app setup to preserve type inference
Files:
packages/runtime/src/index.tspackages/runtime/src/webrtc-signaling.tspackages/runtime/src/router.ts
packages/*/src/index.ts
📄 CodeRabbit inference engine (AGENTS.md)
Export all public APIs from package index.ts using named exports
Files:
packages/runtime/src/index.tspackages/frontend/src/index.tspackages/react/src/index.ts
packages/frontend/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (packages/frontend/AGENTS.md)
Maintain framework-agnostic code in @agentuity/frontend - do not add React, Svelte, Vue, or other framework-specific dependencies
Files:
packages/frontend/src/index.tspackages/frontend/src/webrtc-manager.ts
packages/frontend/src/**/*.ts
📄 CodeRabbit inference engine (packages/frontend/AGENTS.md)
packages/frontend/src/**/*.ts: Use TypeScript generics heavily for type safety in utility functions
Implement utilities as pure functions where possible
Use only standard browser APIs (fetch, WebSocket, EventSource) - no Node.js APIs
Files:
packages/frontend/src/index.tspackages/frontend/src/webrtc-manager.ts
packages/frontend/src/**/{types,index}.ts
📄 CodeRabbit inference engine (packages/frontend/AGENTS.md)
Route types must be inferred from generated types (RouteRegistry) rather than manually defined
Files:
packages/frontend/src/index.ts
packages/react/**/*.{ts,tsx}
📄 CodeRabbit inference engine (packages/react/AGENTS.md)
packages/react/**/*.{ts,tsx}: Follow React hooks conventions
Use TypeScript generics heavily for type safety
Files:
packages/react/src/webrtc.tsxpackages/react/src/index.ts
packages/react/src/**/*.tsx
📄 CodeRabbit inference engine (packages/react/AGENTS.md)
All components must be functional components
Files:
packages/react/src/webrtc.tsx
packages/react/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (packages/react/AGENTS.md)
Throw errors that can be caught by error boundaries
Files:
packages/react/src/webrtc.tsxpackages/react/src/index.ts
packages/runtime/test/**/*.test.ts
📄 CodeRabbit inference engine (packages/runtime/AGENTS.md)
packages/runtime/test/**/*.test.ts: Useapp.request()for testing routes instead oftestClient()to maintain proper type inference with method-chained apps
Use mock storage interfaces (kv, stream, vector) and TestAgentContext from test/helpers/test-context.ts when testing agents
Import test fixtures from ../src/ not ../ in test files
packages/runtime/test/**/*.test.ts: When testing agents and routes in @agentuity/runtime, do NOT add type annotations to handler parameters; let TypeScript infer types from agent schema
For route testing in @agentuity/runtime, use app.request() instead of Hono's testClient() for proper type inference
Files:
packages/runtime/test/webrtc-signaling.test.ts
packages/*/test/**/*.test.ts
📄 CodeRabbit inference engine (AGENTS.md)
packages/*/test/**/*.test.ts: Place all tests in test/ folder parallel to src/ folder, never in tests/, test/, or within src/ folder
Tests in test/ folder must import source code from ../src/ directory
Use @agentuity/test-utils for shared test helpers instead of creating custom mocks
StructuredError properties are directly on the error instance, not under a .data property
Files:
packages/runtime/test/webrtc-signaling.test.ts
**/tsconfig*.json
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode, ESNext target, bundler moduleResolution, and composite project references
Files:
apps/testing/webrtc-test/tsconfig.json
packages/runtime/src/**/*router*.ts
📄 CodeRabbit inference engine (packages/runtime/AGENTS.md)
packages/runtime/src/**/*router*.ts: UsecreateRouter()to create Hono router instances with extended methods (get, post, put, delete, patch, stream, websocket, sse)
Validate route input usingagent.validator()with automatic type inference from agent schema, or override with custom schemas
Userouter.websocket()for WebSocket routes androuter.sse()for Server-Sent Events routes
Native Bun WebSocket support viahono/bunmust be used for both production and development
Files:
packages/runtime/src/router.ts
packages/cli/src/cmd/*/index.ts
📄 CodeRabbit inference engine (packages/cli/AGENTS.md)
Each command is a directory in
src/cmd/with anindex.tsfile containing aCommandDefinition
Files:
packages/cli/src/cmd/dev/index.ts
🧠 Learnings (3)
📚 Learning: 2025-12-21T00:31:41.858Z
Learnt from: jhaynie
Repo: agentuity/sdk PR: 274
File: packages/cli/src/cmd/build/vite/server-bundler.ts:12-41
Timestamp: 2025-12-21T00:31:41.858Z
Learning: In Bun runtime, BuildMessage and ResolveMessage are global types and are not exported from the bun module. Do not import { BuildMessage } from 'bun' or similar; these types are available globally and should be used without import. This applies to all TypeScript files that target the Bun runtime within the repository.
Applied to files:
apps/testing/webrtc-test/src/agent/hello/agent.tsapps/testing/webrtc-test/src/agent/hello/index.tspackages/cli/src/cmd/build/ast.tsapps/testing/webrtc-test/agentuity.config.tspackages/runtime/src/index.tspackages/frontend/src/index.tsapps/testing/webrtc-test/src/api/index.tsapps/testing/webrtc-test/app.tspackages/runtime/src/webrtc-signaling.tspackages/runtime/test/webrtc-signaling.test.tspackages/frontend/src/webrtc-manager.tspackages/react/src/index.tspackages/runtime/src/router.tspackages/cli/src/cmd/dev/index.ts
📚 Learning: 2025-12-13T14:15:18.261Z
Learnt from: jhaynie
Repo: agentuity/sdk PR: 168
File: packages/runtime/src/session.ts:536-546
Timestamp: 2025-12-13T14:15:18.261Z
Learning: The agentuity/runtime package is Bun-only; during code reviews, do not replace Bun-native APIs (e.g., Bun.CryptoHasher, Bun.serve, and other Bun namespace APIs) with Node.js alternatives. Review changes with the assumption that runtime runs on Bun, and ensure any edits preserve Bun compatibility and do not introduce Node.js-specific fallbacks. Apply this guidance broadly to files under packages/runtime (e.g., packages/runtime/src/...); if there are conditional environment checks, document why Bun is required and avoid dereferencing Bun-only APIs in non-Bun contexts.
Applied to files:
packages/runtime/src/index.tspackages/runtime/src/webrtc-signaling.tspackages/runtime/test/webrtc-signaling.test.tspackages/runtime/src/router.ts
📚 Learning: 2025-12-19T14:19:33.765Z
Learnt from: jhaynie
Repo: agentuity/sdk PR: 259
File: packages/cli/src/cmd/build/vite/registry-generator.ts:306-312
Timestamp: 2025-12-19T14:19:33.765Z
Learning: Route files under src/api should use the .ts extension only (no .tsx) and regex patterns for such paths should anchor to \.ts$ (e.g., /\/.ts$/). Agent files may support both .ts and .tsx, but route files in the Agentuity SDK codebase are restricted to .ts. This guideline applies to all similar route files under src/api across the repository.
Applied to files:
apps/testing/webrtc-test/src/api/index.ts
🧬 Code graph analysis (6)
apps/testing/webrtc-test/src/agent/hello/agent.ts (2)
packages/runtime/src/index.ts (1)
createAgent(19-19)packages/schema/src/index.ts (1)
s(104-150)
packages/react/src/webrtc.tsx (1)
packages/frontend/src/webrtc-manager.ts (8)
WebRTCStatus(4-4)WebRTCManager(71-448)setStatus(128-131)WebRTCManagerOptions(33-46)connect(142-179)hangup(398-416)muteAudio(421-428)muteVideo(433-440)
apps/testing/webrtc-test/src/web/frontend.tsx (2)
packages/react/src/index.ts (1)
AgentuityProvider(3-3)apps/testing/webrtc-test/src/web/App.tsx (1)
App(4-344)
apps/testing/webrtc-test/app.ts (1)
packages/runtime/src/index.ts (1)
createApp(36-36)
packages/runtime/test/webrtc-signaling.test.ts (3)
packages/runtime/src/index.ts (5)
WebSocketConnection(60-60)SignalMsg(64-64)WebRTCRoomManager(68-68)SDPDescription(65-65)ICECandidate(66-66)packages/runtime/src/router.ts (1)
WebSocketConnection(36-41)packages/runtime/src/webrtc-signaling.ts (4)
SignalMsg(17-24)WebRTCRoomManager(40-226)SDPDescription(4-7)ICECandidate(9-14)
packages/frontend/src/webrtc-manager.ts (3)
packages/react/src/webrtc.tsx (1)
WebRTCStatus(10-10)packages/frontend/src/index.ts (5)
WebRTCStatus(23-23)WebRTCCallbacks(24-24)WebRTCManagerOptions(25-25)WebRTCManagerState(26-26)WebRTCManager(22-22)packages/runtime/src/webrtc-signaling.ts (1)
SignalMsg(17-24)
🪛 markdownlint-cli2 (0.18.1)
apps/testing/webrtc-test/src/api/AGENTS.md
11-11: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
33-33: Hard tabs
Column: 1
(MD010, no-hard-tabs)
34-34: Hard tabs
Column: 1
(MD010, no-hard-tabs)
35-35: Hard tabs
Column: 1
(MD010, no-hard-tabs)
36-36: Hard tabs
Column: 1
(MD010, no-hard-tabs)
37-37: Hard tabs
Column: 1
(MD010, no-hard-tabs)
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
89-89: Hard tabs
Column: 1
(MD010, no-hard-tabs)
90-90: Hard tabs
Column: 1
(MD010, no-hard-tabs)
91-91: Hard tabs
Column: 1
(MD010, no-hard-tabs)
92-92: Hard tabs
Column: 1
(MD010, no-hard-tabs)
93-93: Hard tabs
Column: 1
(MD010, no-hard-tabs)
94-94: Hard tabs
Column: 1
(MD010, no-hard-tabs)
95-95: Hard tabs
Column: 1
(MD010, no-hard-tabs)
99-99: Hard tabs
Column: 1
(MD010, no-hard-tabs)
100-100: Hard tabs
Column: 1
(MD010, no-hard-tabs)
101-101: Hard tabs
Column: 1
(MD010, no-hard-tabs)
102-102: Hard tabs
Column: 1
(MD010, no-hard-tabs)
103-103: Hard tabs
Column: 1
(MD010, no-hard-tabs)
104-104: Hard tabs
Column: 1
(MD010, no-hard-tabs)
105-105: Hard tabs
Column: 1
(MD010, no-hard-tabs)
106-106: Hard tabs
Column: 1
(MD010, no-hard-tabs)
107-107: Hard tabs
Column: 1
(MD010, no-hard-tabs)
108-108: Hard tabs
Column: 1
(MD010, no-hard-tabs)
122-122: Hard tabs
Column: 1
(MD010, no-hard-tabs)
123-123: Hard tabs
Column: 1
(MD010, no-hard-tabs)
124-124: Hard tabs
Column: 1
(MD010, no-hard-tabs)
125-125: Hard tabs
Column: 1
(MD010, no-hard-tabs)
126-126: Hard tabs
Column: 1
(MD010, no-hard-tabs)
127-127: Hard tabs
Column: 1
(MD010, no-hard-tabs)
128-128: Hard tabs
Column: 1
(MD010, no-hard-tabs)
167-167: Hard tabs
Column: 1
(MD010, no-hard-tabs)
168-168: Hard tabs
Column: 1
(MD010, no-hard-tabs)
173-173: Hard tabs
Column: 1
(MD010, no-hard-tabs)
174-174: Hard tabs
Column: 1
(MD010, no-hard-tabs)
175-175: Hard tabs
Column: 1
(MD010, no-hard-tabs)
184-184: Hard tabs
Column: 1
(MD010, no-hard-tabs)
185-185: Hard tabs
Column: 1
(MD010, no-hard-tabs)
186-186: Hard tabs
Column: 1
(MD010, no-hard-tabs)
195-195: Hard tabs
Column: 1
(MD010, no-hard-tabs)
196-196: Hard tabs
Column: 1
(MD010, no-hard-tabs)
201-201: Hard tabs
Column: 1
(MD010, no-hard-tabs)
202-202: Hard tabs
Column: 1
(MD010, no-hard-tabs)
203-203: Hard tabs
Column: 1
(MD010, no-hard-tabs)
211-211: Hard tabs
Column: 1
(MD010, no-hard-tabs)
212-212: Hard tabs
Column: 1
(MD010, no-hard-tabs)
213-213: Hard tabs
Column: 1
(MD010, no-hard-tabs)
214-214: Hard tabs
Column: 1
(MD010, no-hard-tabs)
215-215: Hard tabs
Column: 1
(MD010, no-hard-tabs)
216-216: Hard tabs
Column: 1
(MD010, no-hard-tabs)
217-217: Hard tabs
Column: 1
(MD010, no-hard-tabs)
218-218: Hard tabs
Column: 1
(MD010, no-hard-tabs)
219-219: Hard tabs
Column: 1
(MD010, no-hard-tabs)
220-220: Hard tabs
Column: 1
(MD010, no-hard-tabs)
221-221: Hard tabs
Column: 1
(MD010, no-hard-tabs)
222-222: Hard tabs
Column: 1
(MD010, no-hard-tabs)
223-223: Hard tabs
Column: 1
(MD010, no-hard-tabs)
247-247: Hard tabs
Column: 1
(MD010, no-hard-tabs)
apps/testing/webrtc-test/src/agent/AGENTS.md
12-12: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
32-32: Hard tabs
Column: 1
(MD010, no-hard-tabs)
33-33: Hard tabs
Column: 1
(MD010, no-hard-tabs)
34-34: Hard tabs
Column: 1
(MD010, no-hard-tabs)
35-35: Hard tabs
Column: 1
(MD010, no-hard-tabs)
36-36: Hard tabs
Column: 1
(MD010, no-hard-tabs)
37-37: Hard tabs
Column: 1
(MD010, no-hard-tabs)
38-38: Hard tabs
Column: 1
(MD010, no-hard-tabs)
39-39: Hard tabs
Column: 1
(MD010, no-hard-tabs)
40-40: Hard tabs
Column: 1
(MD010, no-hard-tabs)
41-41: Hard tabs
Column: 1
(MD010, no-hard-tabs)
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
44-44: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
60-60: Hard tabs
Column: 1
(MD010, no-hard-tabs)
61-61: Hard tabs
Column: 1
(MD010, no-hard-tabs)
62-62: Hard tabs
Column: 1
(MD010, no-hard-tabs)
63-63: Hard tabs
Column: 1
(MD010, no-hard-tabs)
64-64: Hard tabs
Column: 1
(MD010, no-hard-tabs)
65-65: Hard tabs
Column: 1
(MD010, no-hard-tabs)
66-66: Hard tabs
Column: 1
(MD010, no-hard-tabs)
67-67: Hard tabs
Column: 1
(MD010, no-hard-tabs)
68-68: Hard tabs
Column: 1
(MD010, no-hard-tabs)
69-69: Hard tabs
Column: 1
(MD010, no-hard-tabs)
70-70: Hard tabs
Column: 1
(MD010, no-hard-tabs)
71-71: Hard tabs
Column: 1
(MD010, no-hard-tabs)
72-72: Hard tabs
Column: 1
(MD010, no-hard-tabs)
73-73: Hard tabs
Column: 1
(MD010, no-hard-tabs)
74-74: Hard tabs
Column: 1
(MD010, no-hard-tabs)
75-75: Hard tabs
Column: 1
(MD010, no-hard-tabs)
76-76: Hard tabs
Column: 1
(MD010, no-hard-tabs)
77-77: Hard tabs
Column: 1
(MD010, no-hard-tabs)
78-78: Hard tabs
Column: 1
(MD010, no-hard-tabs)
79-79: Hard tabs
Column: 1
(MD010, no-hard-tabs)
80-80: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
82-82: Hard tabs
Column: 1
(MD010, no-hard-tabs)
83-83: Hard tabs
Column: 1
(MD010, no-hard-tabs)
84-84: Hard tabs
Column: 1
(MD010, no-hard-tabs)
94-94: Hard tabs
Column: 1
(MD010, no-hard-tabs)
98-98: Hard tabs
Column: 1
(MD010, no-hard-tabs)
102-102: Hard tabs
Column: 1
(MD010, no-hard-tabs)
120-120: Hard tabs
Column: 1
(MD010, no-hard-tabs)
121-121: Hard tabs
Column: 1
(MD010, no-hard-tabs)
126-126: Hard tabs
Column: 1
(MD010, no-hard-tabs)
127-127: Hard tabs
Column: 1
(MD010, no-hard-tabs)
128-128: Hard tabs
Column: 1
(MD010, no-hard-tabs)
160-160: Hard tabs
Column: 1
(MD010, no-hard-tabs)
161-161: Hard tabs
Column: 1
(MD010, no-hard-tabs)
162-162: Hard tabs
Column: 1
(MD010, no-hard-tabs)
170-170: Hard tabs
Column: 1
(MD010, no-hard-tabs)
171-171: Hard tabs
Column: 1
(MD010, no-hard-tabs)
172-172: Hard tabs
Column: 1
(MD010, no-hard-tabs)
173-173: Hard tabs
Column: 1
(MD010, no-hard-tabs)
174-174: Hard tabs
Column: 1
(MD010, no-hard-tabs)
175-175: Hard tabs
Column: 1
(MD010, no-hard-tabs)
176-176: Hard tabs
Column: 1
(MD010, no-hard-tabs)
184-184: Hard tabs
Column: 1
(MD010, no-hard-tabs)
185-185: Hard tabs
Column: 1
(MD010, no-hard-tabs)
195-195: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
215-215: Hard tabs
Column: 1
(MD010, no-hard-tabs)
216-216: Hard tabs
Column: 1
(MD010, no-hard-tabs)
217-217: Hard tabs
Column: 1
(MD010, no-hard-tabs)
218-218: Hard tabs
Column: 1
(MD010, no-hard-tabs)
219-219: Hard tabs
Column: 1
(MD010, no-hard-tabs)
220-220: Hard tabs
Column: 1
(MD010, no-hard-tabs)
221-221: Hard tabs
Column: 1
(MD010, no-hard-tabs)
222-222: Hard tabs
Column: 1
(MD010, no-hard-tabs)
223-223: Hard tabs
Column: 1
(MD010, no-hard-tabs)
224-224: Hard tabs
Column: 1
(MD010, no-hard-tabs)
225-225: Hard tabs
Column: 1
(MD010, no-hard-tabs)
226-226: Hard tabs
Column: 1
(MD010, no-hard-tabs)
227-227: Hard tabs
Column: 1
(MD010, no-hard-tabs)
228-228: Hard tabs
Column: 1
(MD010, no-hard-tabs)
229-229: Hard tabs
Column: 1
(MD010, no-hard-tabs)
230-230: Hard tabs
Column: 1
(MD010, no-hard-tabs)
243-243: Hard tabs
Column: 1
(MD010, no-hard-tabs)
244-244: Hard tabs
Column: 1
(MD010, no-hard-tabs)
245-245: Hard tabs
Column: 1
(MD010, no-hard-tabs)
246-246: Hard tabs
Column: 1
(MD010, no-hard-tabs)
247-247: Hard tabs
Column: 1
(MD010, no-hard-tabs)
248-248: Hard tabs
Column: 1
(MD010, no-hard-tabs)
249-249: Hard tabs
Column: 1
(MD010, no-hard-tabs)
250-250: Hard tabs
Column: 1
(MD010, no-hard-tabs)
251-251: Hard tabs
Column: 1
(MD010, no-hard-tabs)
252-252: Hard tabs
Column: 1
(MD010, no-hard-tabs)
253-253: Hard tabs
Column: 1
(MD010, no-hard-tabs)
254-254: Hard tabs
Column: 1
(MD010, no-hard-tabs)
255-255: Hard tabs
Column: 1
(MD010, no-hard-tabs)
256-256: Hard tabs
Column: 1
(MD010, no-hard-tabs)
257-257: Hard tabs
Column: 1
(MD010, no-hard-tabs)
258-258: Hard tabs
Column: 1
(MD010, no-hard-tabs)
259-259: Hard tabs
Column: 1
(MD010, no-hard-tabs)
260-260: Hard tabs
Column: 1
(MD010, no-hard-tabs)
261-261: Hard tabs
Column: 1
(MD010, no-hard-tabs)
262-262: Hard tabs
Column: 1
(MD010, no-hard-tabs)
263-263: Hard tabs
Column: 1
(MD010, no-hard-tabs)
264-264: Hard tabs
Column: 1
(MD010, no-hard-tabs)
265-265: Hard tabs
Column: 1
(MD010, no-hard-tabs)
266-266: Hard tabs
Column: 1
(MD010, no-hard-tabs)
267-267: Hard tabs
Column: 1
(MD010, no-hard-tabs)
268-268: Hard tabs
Column: 1
(MD010, no-hard-tabs)
282-282: Hard tabs
Column: 1
(MD010, no-hard-tabs)
283-283: Hard tabs
Column: 1
(MD010, no-hard-tabs)
284-284: Hard tabs
Column: 1
(MD010, no-hard-tabs)
285-285: Hard tabs
Column: 1
(MD010, no-hard-tabs)
286-286: Hard tabs
Column: 1
(MD010, no-hard-tabs)
287-287: Hard tabs
Column: 1
(MD010, no-hard-tabs)
288-288: Hard tabs
Column: 1
(MD010, no-hard-tabs)
289-289: Hard tabs
Column: 1
(MD010, no-hard-tabs)
apps/testing/webrtc-test/README.md
19-19: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
97-97: Hard tabs
Column: 1
(MD010, no-hard-tabs)
98-98: Hard tabs
Column: 1
(MD010, no-hard-tabs)
99-99: Hard tabs
Column: 1
(MD010, no-hard-tabs)
100-100: Hard tabs
Column: 1
(MD010, no-hard-tabs)
101-101: Hard tabs
Column: 1
(MD010, no-hard-tabs)
102-102: Hard tabs
Column: 1
(MD010, no-hard-tabs)
103-103: Hard tabs
Column: 1
(MD010, no-hard-tabs)
104-104: Hard tabs
Column: 1
(MD010, no-hard-tabs)
105-105: Hard tabs
Column: 1
(MD010, no-hard-tabs)
106-106: Hard tabs
Column: 1
(MD010, no-hard-tabs)
124-124: Hard tabs
Column: 1
(MD010, no-hard-tabs)
125-125: Hard tabs
Column: 1
(MD010, no-hard-tabs)
129-129: Hard tabs
Column: 1
(MD010, no-hard-tabs)
130-130: Hard tabs
Column: 1
(MD010, no-hard-tabs)
131-131: Hard tabs
Column: 1
(MD010, no-hard-tabs)
apps/testing/webrtc-test/src/web/AGENTS.md
14-14: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
34-34: Hard tabs
Column: 1
(MD010, no-hard-tabs)
35-35: Hard tabs
Column: 1
(MD010, no-hard-tabs)
37-37: Hard tabs
Column: 1
(MD010, no-hard-tabs)
38-38: Hard tabs
Column: 1
(MD010, no-hard-tabs)
39-39: Hard tabs
Column: 1
(MD010, no-hard-tabs)
40-40: Hard tabs
Column: 1
(MD010, no-hard-tabs)
41-41: Hard tabs
Column: 1
(MD010, no-hard-tabs)
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
44-44: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
47-47: Hard tabs
Column: 1
(MD010, no-hard-tabs)
48-48: Hard tabs
Column: 1
(MD010, no-hard-tabs)
49-49: Hard tabs
Column: 1
(MD010, no-hard-tabs)
50-50: Hard tabs
Column: 1
(MD010, no-hard-tabs)
51-51: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
81-81: Hard tabs
Column: 1
(MD010, no-hard-tabs)
82-82: Hard tabs
Column: 1
(MD010, no-hard-tabs)
83-83: Hard tabs
Column: 1
(MD010, no-hard-tabs)
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
100-100: Hard tabs
Column: 1
(MD010, no-hard-tabs)
101-101: Hard tabs
Column: 1
(MD010, no-hard-tabs)
102-102: Hard tabs
Column: 1
(MD010, no-hard-tabs)
103-103: Hard tabs
Column: 1
(MD010, no-hard-tabs)
104-104: Hard tabs
Column: 1
(MD010, no-hard-tabs)
105-105: Hard tabs
Column: 1
(MD010, no-hard-tabs)
106-106: Hard tabs
Column: 1
(MD010, no-hard-tabs)
116-116: Hard tabs
Column: 1
(MD010, no-hard-tabs)
117-117: Hard tabs
Column: 1
(MD010, no-hard-tabs)
118-118: Hard tabs
Column: 1
(MD010, no-hard-tabs)
119-119: Hard tabs
Column: 1
(MD010, no-hard-tabs)
120-120: Hard tabs
Column: 1
(MD010, no-hard-tabs)
121-121: Hard tabs
Column: 1
(MD010, no-hard-tabs)
122-122: Hard tabs
Column: 1
(MD010, no-hard-tabs)
123-123: Hard tabs
Column: 1
(MD010, no-hard-tabs)
124-124: Hard tabs
Column: 1
(MD010, no-hard-tabs)
134-134: Hard tabs
Column: 1
(MD010, no-hard-tabs)
135-135: Hard tabs
Column: 1
(MD010, no-hard-tabs)
136-136: Hard tabs
Column: 1
(MD010, no-hard-tabs)
137-137: Hard tabs
Column: 1
(MD010, no-hard-tabs)
138-138: Hard tabs
Column: 1
(MD010, no-hard-tabs)
139-139: Hard tabs
Column: 1
(MD010, no-hard-tabs)
140-140: Hard tabs
Column: 1
(MD010, no-hard-tabs)
141-141: Hard tabs
Column: 1
(MD010, no-hard-tabs)
142-142: Hard tabs
Column: 1
(MD010, no-hard-tabs)
153-153: Hard tabs
Column: 1
(MD010, no-hard-tabs)
154-154: Hard tabs
Column: 1
(MD010, no-hard-tabs)
155-155: Hard tabs
Column: 1
(MD010, no-hard-tabs)
156-156: Hard tabs
Column: 1
(MD010, no-hard-tabs)
157-157: Hard tabs
Column: 1
(MD010, no-hard-tabs)
158-158: Hard tabs
Column: 1
(MD010, no-hard-tabs)
159-159: Hard tabs
Column: 1
(MD010, no-hard-tabs)
160-160: Hard tabs
Column: 1
(MD010, no-hard-tabs)
161-161: Hard tabs
Column: 1
(MD010, no-hard-tabs)
162-162: Hard tabs
Column: 1
(MD010, no-hard-tabs)
163-163: Hard tabs
Column: 1
(MD010, no-hard-tabs)
165-165: Hard tabs
Column: 1
(MD010, no-hard-tabs)
166-166: Hard tabs
Column: 1
(MD010, no-hard-tabs)
167-167: Hard tabs
Column: 1
(MD010, no-hard-tabs)
168-168: Hard tabs
Column: 1
(MD010, no-hard-tabs)
169-169: Hard tabs
Column: 1
(MD010, no-hard-tabs)
170-170: Hard tabs
Column: 1
(MD010, no-hard-tabs)
171-171: Hard tabs
Column: 1
(MD010, no-hard-tabs)
172-172: Hard tabs
Column: 1
(MD010, no-hard-tabs)
173-173: Hard tabs
Column: 1
(MD010, no-hard-tabs)
174-174: Hard tabs
Column: 1
(MD010, no-hard-tabs)
175-175: Hard tabs
Column: 1
(MD010, no-hard-tabs)
176-176: Hard tabs
Column: 1
(MD010, no-hard-tabs)
177-177: Hard tabs
Column: 1
(MD010, no-hard-tabs)
178-178: Hard tabs
Column: 1
(MD010, no-hard-tabs)
179-179: Hard tabs
Column: 1
(MD010, no-hard-tabs)
180-180: Hard tabs
Column: 1
(MD010, no-hard-tabs)
181-181: Hard tabs
Column: 1
(MD010, no-hard-tabs)
182-182: Hard tabs
Column: 1
(MD010, no-hard-tabs)
183-183: Hard tabs
Column: 1
(MD010, no-hard-tabs)
184-184: Hard tabs
Column: 1
(MD010, no-hard-tabs)
185-185: Hard tabs
Column: 1
(MD010, no-hard-tabs)
186-186: Hard tabs
Column: 1
(MD010, no-hard-tabs)
187-187: Hard tabs
Column: 1
(MD010, no-hard-tabs)
188-188: Hard tabs
Column: 1
(MD010, no-hard-tabs)
189-189: Hard tabs
Column: 1
(MD010, no-hard-tabs)
190-190: Hard tabs
Column: 1
(MD010, no-hard-tabs)
198-198: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
224-224: Hard tabs
Column: 1
(MD010, no-hard-tabs)
234-234: Hard tabs
Column: 1
(MD010, no-hard-tabs)
235-235: Hard tabs
Column: 1
(MD010, no-hard-tabs)
236-236: Hard tabs
Column: 1
(MD010, no-hard-tabs)
250-250: Hard tabs
Column: 1
(MD010, no-hard-tabs)
251-251: Hard tabs
Column: 1
(MD010, no-hard-tabs)
252-252: Hard tabs
Column: 1
(MD010, no-hard-tabs)
253-253: Hard tabs
Column: 1
(MD010, no-hard-tabs)
254-254: Hard tabs
Column: 1
(MD010, no-hard-tabs)
255-255: Hard tabs
Column: 1
(MD010, no-hard-tabs)
256-256: Hard tabs
Column: 1
(MD010, no-hard-tabs)
257-257: Hard tabs
Column: 1
(MD010, no-hard-tabs)
258-258: Hard tabs
Column: 1
(MD010, no-hard-tabs)
259-259: Hard tabs
Column: 1
(MD010, no-hard-tabs)
apps/testing/webrtc-test/AGENTS.md
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🔇 Additional comments (40)
packages/cli/src/cmd/build/ast.ts (1)
1339-1349: LGTM! WebRTC signaling endpoint implementation follows existing patterns correctly.The implementation properly configures WebRTC signaling as a WebSocket endpoint using HTTP GET (standard for WebSocket upgrade) and correctly appends
/signalto the path suffix. The logic is consistent with similar routing methods likestream,sse, andwebsocket.packages/cli/src/cmd/dev/index.ts (1)
135-137: LGTM - Formatting improvement.The multi-line type annotation improves readability for this complex return type.
apps/testing/webrtc-test/agentuity.json (1)
1-13: LGTM!Standard Agentuity project configuration with reasonable resource limits for a test application.
apps/testing/webrtc-test/app.ts (1)
1-17: LGTM!Clean application entry point following the
createApppattern. The empty setup/shutdown functions serve as documented placeholders for future resource initialization.apps/testing/webrtc-test/.vscode/settings.json (1)
1-16: LGTM!Standard VSCode workspace settings with appropriate search exclusions and schema mapping for
agentuity.json.apps/testing/webrtc-test/tsconfig.json (1)
1-27: LGTM!TypeScript configuration follows project conventions with strict mode, ESNext target, and bundler module resolution. Path aliases are properly configured for the agent and API modules.
packages/runtime/src/router.ts (2)
17-17: LGTM!Import of
WebRTCRoomManagerandWebRTCOptionsfor the new WebRTC signaling capability.
278-299: LGTM!Well-documented type declaration for the new
webrtc()method with clear JSDoc explaining the signaling endpoint behavior.apps/testing/webrtc-test/src/agent/hello/index.ts (1)
1-1: LGTM!Standard barrel export pattern for re-exporting the agent module.
apps/testing/webrtc-test/src/web/index.html (1)
1-13: LGTM!Well-structured HTML entry point with proper meta tags, async module loading, and React root element.
apps/testing/webrtc-test/src/agent/hello/agent.ts (1)
1-15: LGTM!Clean agent implementation following Agentuity conventions. The schema definition, handler signature, and export pattern are all correct.
apps/testing/webrtc-test/src/web/frontend.tsx (1)
1-29: LGTM!Excellent React 18 entry point with proper HMR support. The use of
import.meta.hot.datafor root persistence and the conditional rendering logic are both correct.packages/frontend/src/index.ts (1)
21-27: LGTM!The new WebRTC export block follows the established pattern for manager exports and maintains framework-agnostic naming conventions consistent with the package's design.
packages/runtime/src/index.ts (1)
62-69: LGTM!The WebRTC signaling exports are well-positioned and follow the package's export conventions. The types and manager class provide a clean API surface for server-side WebRTC functionality.
apps/testing/webrtc-test/agentuity.config.ts (1)
1-35: LGTM!Clean configuration file with proper type constraint using
satisfies AgentuityConfig. The workbench setup is appropriate for a development/test application.apps/testing/webrtc-test/src/api/index.ts (1)
1-16: LGTM!The API router is well-structured with proper validation flow for the
/helloendpoint and clean WebRTC signaling integration viaapi.webrtc('/call'). The implementation follows the expected patterns for route files.apps/testing/webrtc-test/AGENTS.md (1)
1-64: LGTM!Comprehensive project documentation covering commands, CLI usage, and web frontend structure. The content accurately describes the build system and development workflow.
packages/react/src/index.ts (2)
33-38: LGTM!WebRTC hook and types are properly exported from the local module, following the established export pattern.
74-77: LGTM!WebRTCManager and related types are correctly re-exported from
@agentuity/frontend, providing a convenient single import point for React consumers.packages/runtime/test/webrtc-signaling.test.ts (5)
1-27: LGTM!Clean test setup with a minimal mock WebSocket implementation and a helpful
parseMessageutility. The imports correctly reference../src/per coding guidelines.
36-110: LGTM!Thorough coverage of the join flow including peer assignment, existing peers list, notifications, room capacity limits, and multi-room scenarios.
112-158: LGTM!Good disconnect handling tests covering peer removal, notifications, rejoin capability, and automatic room cleanup.
160-233: LGTM!SDP and ICE relay tests properly verify targeted delivery, broadcast behavior, server-injected
fromfield, and error handling for peers not in a room.
235-323: LGTM!Message parsing, routing, error handling, room stats, and custom
maxPeersconfiguration are all well-tested. The test suite provides good coverage of theWebRTCRoomManagerfunctionality.packages/react/src/webrtc.tsx (4)
1-10: LGTM!Clean imports with proper type re-export for
WebRTCStatus. The hook follows React conventions by importing necessary hooks from React.
15-28: LGTM!Well-defined interface with good JSDoc comments. The options properly mirror
WebRTCManagerOptionswhile adding React-specificautoConnectoption.
33-58: LGTM!Comprehensive return type interface with all necessary properties for a WebRTC call hook. Good use of nullable types for peer IDs and error state.
199-212: LGTM!Clean return object with all necessary properties. The hook provides a comprehensive API for WebRTC calls.
packages/runtime/src/webrtc-signaling.ts (5)
1-14: LGTM!Good decision to define custom SDP and ICE candidate interfaces rather than using DOM types, since this code runs on the server (Bun runtime). The types properly match the WebRTC specification.
16-24: LGTM!Well-defined discriminated union for signaling messages. The
tfield provides clean message type discrimination for the switch statement inhandleMessage.
106-126: LGTM!Clean disconnect handling with proper cleanup of empty rooms and notification to remaining peers.
219-225: LGTM!Useful debugging utility. Consider exposing room-level stats if needed for monitoring.
56-58: WebSocketConnection interface does not have a readyState property.The
WebSocketConnectioninterface defined inpackages/runtime/src/router.tsonly exposesonOpen,onMessage,onClose, andsendmethods. The proposed fix referencesws.readyState, which does not exist on this interface and would not compile.While the client-side WebSocketManager does check
ws.readyState === WebSocket.OPENbefore sending, the server-sideWebSocketConnectioninterface is not designed to expose connection state. A readyState check cannot be added as proposed.Likely an incorrect or invalid review comment.
packages/frontend/src/webrtc-manager.ts (7)
1-16: LGTM!Clean type definitions. Note that
SignalMsgusesRTCSessionDescriptionInitandRTCIceCandidateInit(browser types) which correctly match what the browser WebRTC API expects, while the server uses custom types. The protocol is compatible since both serialize to the same JSON structure.
181-226: LGTM - Well-implemented signaling state machine.Good handling of the different signaling messages. The polite/impolite role assignment based on join order correctly implements the perfect negotiation pattern.
279-295: LGTM - Correct perfect negotiation implementation.The
onnegotiationneededhandler properly setsmakingOfferflag, creates offer viasetLocalDescription()(implicit offer creation), and sends it. The finally block ensures the flag is cleared even on error.
325-364: LGTM - Solid SDP handling with candidate buffering.Correct implementation of collision detection for perfect negotiation. The ICE candidate buffer flush after setting remote description is essential and properly implemented.
348-350:console.warnis acceptable here per coding guidelines.The coding guidelines specify
ctx.loggerfor runtime code, but this is the frontend package which should use standard browser APIs.console.warnis appropriate for non-critical logging in client-side code.
398-416: LGTM - Clean hangup implementation.Properly stops all local tracks, closes WebSocket and peer connection, and resets state. Matches the pattern described in the relevant code snippets.
442-447: LGTM!
dispose()correctly delegates tohangup()for full cleanup. This provides a clear API for React'suseEffectcleanup.
| _.log | ||
| report.[0-9]_.[0-9]_.[0-9]_.[0-9]\*.json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glob patterns appear malformed.
- Line 19:
_.logwill only match a file literally named_.log, not*.log(all log files). - Line 20: The escaped pattern
report.[0-9]_.[0-9]_.[0-9]_.[0-9]\*.jsonhas unusual escaping that may not match intended files.
If the intent is to ignore all log files and report files, consider:
🔎 Suggested fix
-_.log
-report.[0-9]_.[0-9]_.[0-9]_.[0-9]\*.json
+*.log
+report.*.json📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| _.log | |
| report.[0-9]_.[0-9]_.[0-9]_.[0-9]\*.json | |
| *.log | |
| report.*.json |
🤖 Prompt for AI Agents
In apps/testing/webrtc-test/.gitignore around lines 19-20, the glob patterns are
malformed: change "_.log" to "*.log" to ignore all log files, and replace the
escaped report pattern with a proper glob such as
"report.[0-9]_[0-9]_[0-9]_[0-9]*.json" (or "report.*.json" if you want to match
any report filename) so the intended report files are actually ignored.
| .env | ||
| .env.\* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escaped asterisk in .env.\* pattern.
The backslash may cause this pattern to not match .env.local, .env.production, etc. as intended.
🔎 Suggested fix
-.env.\*
+.env.*📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .env | |
| .env.\* | |
| .env | |
| .env.* |
🤖 Prompt for AI Agents
In apps/testing/webrtc-test/.gitignore around lines 24-25, the pattern ".env.\*"
is escaped and won't match files like .env.local or .env.production; replace the
escaped pattern with an unescaped glob such as ".env.*" (or ".env*" if you also
want to match ".env") so the intended environment files are ignored.
| export function App() { | ||
| const [name, setName] = useState('World'); | ||
| const { run, running, data: greeting } = useAgent('hello'); | ||
|
|
||
| return ( | ||
| <div style={{ fontFamily: 'sans-serif', padding: '2rem' }}> | ||
| <AgentuityProvider> | ||
| <h1>Welcome to Agentuity</h1> | ||
|
|
||
| <input | ||
| type="text" | ||
| value={name} | ||
| onChange={(e) => setName(e.target.value)} | ||
| disabled={running} | ||
| /> | ||
|
|
||
| <button | ||
| onClick={() => run({ name })} | ||
| disabled={running} | ||
| > | ||
| {running ? 'Running...' : 'Say Hello'} | ||
| </button> | ||
|
|
||
| <div>{greeting ?? 'Waiting for response'}</div> | ||
| </AgentuityProvider> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect AgentuityProvider placement in example.
The useAgent hook is called at line 35 before AgentuityProvider wraps the component tree (lines 39-57). React context hooks must be called from components inside the provider. This example will fail at runtime.
🔎 Suggested fix
export function App() {
- const [name, setName] = useState('World');
- const { run, running, data: greeting } = useAgent('hello');
-
return (
- <div style={{ fontFamily: 'sans-serif', padding: '2rem' }}>
- <AgentuityProvider>
- <h1>Welcome to Agentuity</h1>
-
- <input
- type="text"
- value={name}
- onChange={(e) => setName(e.target.value)}
- disabled={running}
- />
-
- <button
- onClick={() => run({ name })}
- disabled={running}
- >
- {running ? 'Running...' : 'Say Hello'}
- </button>
-
- <div>{greeting ?? 'Waiting for response'}</div>
- </AgentuityProvider>
- </div>
+ <AgentuityProvider>
+ <HelloContent />
+ </AgentuityProvider>
);
}
+
+function HelloContent() {
+ const [name, setName] = useState('World');
+ const { run, running, data: greeting } = useAgent('hello');
+
+ return (
+ <div style={{ fontFamily: 'sans-serif', padding: '2rem' }}>
+ <h1>Welcome to Agentuity</h1>
+ <input
+ type="text"
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ disabled={running}
+ />
+ <button
+ onClick={() => run({ name })}
+ disabled={running}
+ >
+ {running ? 'Running...' : 'Say Hello'}
+ </button>
+ <div>{greeting ?? 'Waiting for response'}</div>
+ </div>
+ );
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function App() { | |
| const [name, setName] = useState('World'); | |
| const { run, running, data: greeting } = useAgent('hello'); | |
| return ( | |
| <div style={{ fontFamily: 'sans-serif', padding: '2rem' }}> | |
| <AgentuityProvider> | |
| <h1>Welcome to Agentuity</h1> | |
| <input | |
| type="text" | |
| value={name} | |
| onChange={(e) => setName(e.target.value)} | |
| disabled={running} | |
| /> | |
| <button | |
| onClick={() => run({ name })} | |
| disabled={running} | |
| > | |
| {running ? 'Running...' : 'Say Hello'} | |
| </button> | |
| <div>{greeting ?? 'Waiting for response'}</div> | |
| </AgentuityProvider> | |
| </div> | |
| ); | |
| } | |
| export function App() { | |
| return ( | |
| <AgentuityProvider> | |
| <HelloContent /> | |
| </AgentuityProvider> | |
| ); | |
| } | |
| function HelloContent() { | |
| const [name, setName] = useState('World'); | |
| const { run, running, data: greeting } = useAgent('hello'); | |
| return ( | |
| <div style={{ fontFamily: 'sans-serif', padding: '2rem' }}> | |
| <h1>Welcome to Agentuity</h1> | |
| <input | |
| type="text" | |
| value={name} | |
| onChange={(e) => setName(e.target.value)} | |
| disabled={running} | |
| /> | |
| <button | |
| onClick={() => run({ name })} | |
| disabled={running} | |
| > | |
| {running ? 'Running...' : 'Say Hello'} | |
| </button> | |
| <div>{greeting ?? 'Waiting for response'}</div> | |
| </div> | |
| ); | |
| } |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
34-34: Hard tabs
Column: 1
(MD010, no-hard-tabs)
35-35: Hard tabs
Column: 1
(MD010, no-hard-tabs)
37-37: Hard tabs
Column: 1
(MD010, no-hard-tabs)
38-38: Hard tabs
Column: 1
(MD010, no-hard-tabs)
39-39: Hard tabs
Column: 1
(MD010, no-hard-tabs)
40-40: Hard tabs
Column: 1
(MD010, no-hard-tabs)
41-41: Hard tabs
Column: 1
(MD010, no-hard-tabs)
42-42: Hard tabs
Column: 1
(MD010, no-hard-tabs)
43-43: Hard tabs
Column: 1
(MD010, no-hard-tabs)
44-44: Hard tabs
Column: 1
(MD010, no-hard-tabs)
45-45: Hard tabs
Column: 1
(MD010, no-hard-tabs)
46-46: Hard tabs
Column: 1
(MD010, no-hard-tabs)
47-47: Hard tabs
Column: 1
(MD010, no-hard-tabs)
48-48: Hard tabs
Column: 1
(MD010, no-hard-tabs)
49-49: Hard tabs
Column: 1
(MD010, no-hard-tabs)
50-50: Hard tabs
Column: 1
(MD010, no-hard-tabs)
51-51: Hard tabs
Column: 1
(MD010, no-hard-tabs)
52-52: Hard tabs
Column: 1
(MD010, no-hard-tabs)
53-53: Hard tabs
Column: 1
(MD010, no-hard-tabs)
54-54: Hard tabs
Column: 1
(MD010, no-hard-tabs)
55-55: Hard tabs
Column: 1
(MD010, no-hard-tabs)
56-56: Hard tabs
Column: 1
(MD010, no-hard-tabs)
57-57: Hard tabs
Column: 1
(MD010, no-hard-tabs)
58-58: Hard tabs
Column: 1
(MD010, no-hard-tabs)
59-59: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🤖 Prompt for AI Agents
In apps/testing/webrtc-test/src/web/AGENTS.md around lines 33 to 60, the example
calls useAgent() before wrapping the component tree with AgentuityProvider,
which will cause a runtime error; move the AgentuityProvider to wrap the
component (so hooks that consume its context run inside it) by placing
AgentuityProvider above the useAgent hook usage (i.e., ensure the provider is
rendered at a higher level in the component hierarchy or wrap the App contents
so useAgent is invoked from a child of AgentuityProvider).
| export function App() { | ||
| const [count, setCount] = useState(0); | ||
| const { run, data: agentResult } = useAgent('simple'); | ||
| const { connected, send, data: wsMessage } = useAgentWebsocket('websocket'); | ||
|
|
||
| useEffect(() => { | ||
| // Send WebSocket message every second | ||
| const interval = setInterval(() => { | ||
| send(`Message at ${new Date().toISOString()}`); | ||
| }, 1000); | ||
| return () => clearInterval(interval); | ||
| }, [send]); | ||
|
|
||
| return ( | ||
| <div style={{ fontFamily: 'sans-serif', padding: '2rem' }}> | ||
| <AgentuityProvider> | ||
| <h1>My Agentuity App</h1> | ||
|
|
||
| <div> | ||
| <p>Count: {count}</p> | ||
| <button onClick={() => setCount(c => c + 1)}> | ||
| Increment | ||
| </button> | ||
| </div> | ||
|
|
||
| <div> | ||
| <button onClick={() => run({ name: 'Jeff', age: 30 })}> | ||
| Call Agent | ||
| </button> | ||
| <p>{agentResult}</p> | ||
| </div> | ||
|
|
||
| <div> | ||
| <strong>WebSocket:</strong> | ||
| {connected ? JSON.stringify(wsMessage) : 'Not connected'} | ||
| </div> | ||
| </AgentuityProvider> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same AgentuityProvider placement issue in complete example.
The hooks useAgent and useAgentWebsocket (lines 154-155) are called outside the AgentuityProvider (line 167). This pattern will fail at runtime.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
153-153: Hard tabs
Column: 1
(MD010, no-hard-tabs)
154-154: Hard tabs
Column: 1
(MD010, no-hard-tabs)
155-155: Hard tabs
Column: 1
(MD010, no-hard-tabs)
156-156: Hard tabs
Column: 1
(MD010, no-hard-tabs)
157-157: Hard tabs
Column: 1
(MD010, no-hard-tabs)
158-158: Hard tabs
Column: 1
(MD010, no-hard-tabs)
159-159: Hard tabs
Column: 1
(MD010, no-hard-tabs)
160-160: Hard tabs
Column: 1
(MD010, no-hard-tabs)
161-161: Hard tabs
Column: 1
(MD010, no-hard-tabs)
162-162: Hard tabs
Column: 1
(MD010, no-hard-tabs)
163-163: Hard tabs
Column: 1
(MD010, no-hard-tabs)
165-165: Hard tabs
Column: 1
(MD010, no-hard-tabs)
166-166: Hard tabs
Column: 1
(MD010, no-hard-tabs)
167-167: Hard tabs
Column: 1
(MD010, no-hard-tabs)
168-168: Hard tabs
Column: 1
(MD010, no-hard-tabs)
169-169: Hard tabs
Column: 1
(MD010, no-hard-tabs)
170-170: Hard tabs
Column: 1
(MD010, no-hard-tabs)
171-171: Hard tabs
Column: 1
(MD010, no-hard-tabs)
172-172: Hard tabs
Column: 1
(MD010, no-hard-tabs)
173-173: Hard tabs
Column: 1
(MD010, no-hard-tabs)
174-174: Hard tabs
Column: 1
(MD010, no-hard-tabs)
175-175: Hard tabs
Column: 1
(MD010, no-hard-tabs)
176-176: Hard tabs
Column: 1
(MD010, no-hard-tabs)
177-177: Hard tabs
Column: 1
(MD010, no-hard-tabs)
178-178: Hard tabs
Column: 1
(MD010, no-hard-tabs)
179-179: Hard tabs
Column: 1
(MD010, no-hard-tabs)
180-180: Hard tabs
Column: 1
(MD010, no-hard-tabs)
181-181: Hard tabs
Column: 1
(MD010, no-hard-tabs)
182-182: Hard tabs
Column: 1
(MD010, no-hard-tabs)
183-183: Hard tabs
Column: 1
(MD010, no-hard-tabs)
184-184: Hard tabs
Column: 1
(MD010, no-hard-tabs)
185-185: Hard tabs
Column: 1
(MD010, no-hard-tabs)
186-186: Hard tabs
Column: 1
(MD010, no-hard-tabs)
187-187: Hard tabs
Column: 1
(MD010, no-hard-tabs)
188-188: Hard tabs
Column: 1
(MD010, no-hard-tabs)
189-189: Hard tabs
Column: 1
(MD010, no-hard-tabs)
190-190: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🤖 Prompt for AI Agents
In apps/testing/webrtc-test/src/web/AGENTS.md around lines 152 to 191, the hooks
useAgent and useAgentWebsocket are being invoked before the AgentuityProvider is
rendered which will break at runtime; fix this by moving the AgentuityProvider
wrapper above the hook usage (or move the hook calls into a child component that
is rendered inside AgentuityProvider) so that both hooks run within the
provider's context, and ensure any state or effects (like the websocket
interval) are defined inside that provider-scoped component.
| async connect(): Promise<void> { | ||
| if (this.status !== 'disconnected') return; | ||
|
|
||
| this.setStatus('connecting'); | ||
|
|
||
| try { | ||
| // Get local media | ||
| const mediaConstraints = this.options.media ?? { video: true, audio: true }; | ||
| this.localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); | ||
| this.callbacks.onLocalStream?.(this.localStream); | ||
|
|
||
| // Connect to signaling server | ||
| this.ws = new WebSocket(this.options.signalUrl); | ||
|
|
||
| this.ws.onopen = () => { | ||
| this.setStatus('signaling'); | ||
| this.send({ t: 'join', roomId: this.options.roomId }); | ||
| }; | ||
|
|
||
| this.ws.onmessage = (event) => { | ||
| const msg = JSON.parse(event.data) as SignalMsg; | ||
| this.handleSignalingMessage(msg); | ||
| }; | ||
|
|
||
| this.ws.onerror = () => { | ||
| this.callbacks.onError?.(new Error('WebSocket connection error')); | ||
| }; | ||
|
|
||
| this.ws.onclose = () => { | ||
| if (this.status !== 'disconnected') { | ||
| this.setStatus('disconnected'); | ||
| } | ||
| }; | ||
| } catch (err) { | ||
| this.setStatus('disconnected'); | ||
| this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing cleanup of local media tracks on connection error.
If WebSocket connection fails after getUserMedia succeeds, the local media tracks remain active (camera/mic stay on). The catch block sets status to disconnected but doesn't stop the tracks.
🔎 Proposed fix
} catch (err) {
+ // Clean up local media on failure
+ if (this.localStream) {
+ for (const track of this.localStream.getTracks()) {
+ track.stop();
+ }
+ this.localStream = null;
+ }
this.setStatus('disconnected');
this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async connect(): Promise<void> { | |
| if (this.status !== 'disconnected') return; | |
| this.setStatus('connecting'); | |
| try { | |
| // Get local media | |
| const mediaConstraints = this.options.media ?? { video: true, audio: true }; | |
| this.localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); | |
| this.callbacks.onLocalStream?.(this.localStream); | |
| // Connect to signaling server | |
| this.ws = new WebSocket(this.options.signalUrl); | |
| this.ws.onopen = () => { | |
| this.setStatus('signaling'); | |
| this.send({ t: 'join', roomId: this.options.roomId }); | |
| }; | |
| this.ws.onmessage = (event) => { | |
| const msg = JSON.parse(event.data) as SignalMsg; | |
| this.handleSignalingMessage(msg); | |
| }; | |
| this.ws.onerror = () => { | |
| this.callbacks.onError?.(new Error('WebSocket connection error')); | |
| }; | |
| this.ws.onclose = () => { | |
| if (this.status !== 'disconnected') { | |
| this.setStatus('disconnected'); | |
| } | |
| }; | |
| } catch (err) { | |
| this.setStatus('disconnected'); | |
| this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); | |
| } | |
| } | |
| async connect(): Promise<void> { | |
| if (this.status !== 'disconnected') return; | |
| this.setStatus('connecting'); | |
| try { | |
| // Get local media | |
| const mediaConstraints = this.options.media ?? { video: true, audio: true }; | |
| this.localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); | |
| this.callbacks.onLocalStream?.(this.localStream); | |
| // Connect to signaling server | |
| this.ws = new WebSocket(this.options.signalUrl); | |
| this.ws.onopen = () => { | |
| this.setStatus('signaling'); | |
| this.send({ t: 'join', roomId: this.options.roomId }); | |
| }; | |
| this.ws.onmessage = (event) => { | |
| const msg = JSON.parse(event.data) as SignalMsg; | |
| this.handleSignalingMessage(msg); | |
| }; | |
| this.ws.onerror = () => { | |
| this.callbacks.onError?.(new Error('WebSocket connection error')); | |
| }; | |
| this.ws.onclose = () => { | |
| if (this.status !== 'disconnected') { | |
| this.setStatus('disconnected'); | |
| } | |
| }; | |
| } catch (err) { | |
| // Clean up local media on failure | |
| if (this.localStream) { | |
| for (const track of this.localStream.getTracks()) { | |
| track.stop(); | |
| } | |
| this.localStream = null; | |
| } | |
| this.setStatus('disconnected'); | |
| this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err))); | |
| } | |
| } |
🤖 Prompt for AI Agents
In packages/frontend/src/webrtc-manager.ts around lines 142 to 179, if
getUserMedia succeeds but the connection fails the local media tracks aren't
stopped; update the cleanup logic so that whenever we transition to
'disconnected' due to an error (in the catch block and in the ws.onerror/onclose
paths that set status to 'disconnected') we stop all tracks on this.localStream
(this.localStream.getTracks().forEach(t => t.stop())), clear this.localStream
(set to undefined/null) and notify via callbacks if appropriate (e.g.
this.callbacks.onLocalStream?.(undefined) or similar) so the camera/mic are
actually turned off and state is consistent.
| this.ws.onmessage = (event) => { | ||
| const msg = JSON.parse(event.data) as SignalMsg; | ||
| this.handleSignalingMessage(msg); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unhandled JSON parse error in WebSocket message handler.
If event.data is not valid JSON, JSON.parse will throw and the error won't be caught, potentially crashing the handler.
🔎 Proposed fix
this.ws.onmessage = (event) => {
- const msg = JSON.parse(event.data) as SignalMsg;
- this.handleSignalingMessage(msg);
+ try {
+ const msg = JSON.parse(event.data) as SignalMsg;
+ this.handleSignalingMessage(msg);
+ } catch (err) {
+ this.callbacks.onError?.(new Error('Invalid signaling message'));
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.ws.onmessage = (event) => { | |
| const msg = JSON.parse(event.data) as SignalMsg; | |
| this.handleSignalingMessage(msg); | |
| }; | |
| this.ws.onmessage = (event) => { | |
| try { | |
| const msg = JSON.parse(event.data) as SignalMsg; | |
| this.handleSignalingMessage(msg); | |
| } catch (err) { | |
| this.callbacks.onError?.(new Error('Invalid signaling message')); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In packages/frontend/src/webrtc-manager.ts around lines 161 to 164, the
WebSocket onmessage handler directly calls JSON.parse(event.data) which can
throw if event.data is not valid JSON; wrap the parse in a try/catch (and
optionally verify event.data is a string) so malformed messages do not crash the
handler, log the parse error with context, and only call
this.handleSignalingMessage(msg) when parsing succeeds and the resulting object
matches the expected SignalMsg shape.
| handleMessage(ws: WebSocketConnection, data: string): void { | ||
| let msg: SignalMsg; | ||
| try { | ||
| msg = JSON.parse(data); | ||
| } catch { | ||
| this.send(ws, { t: 'error', message: 'Invalid JSON' }); | ||
| return; | ||
| } | ||
|
|
||
| switch (msg.t) { | ||
| case 'join': | ||
| this.handleJoin(ws, msg.roomId); | ||
| break; | ||
| case 'sdp': | ||
| this.handleSDP(ws, msg.to, msg.description); | ||
| break; | ||
| case 'ice': | ||
| this.handleICE(ws, msg.to, msg.candidate); | ||
| break; | ||
| default: | ||
| this.send(ws, { | ||
| t: 'error', | ||
| message: `Unknown message type: ${(msg as { t: string }).t}`, | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation for required message fields.
The handleMessage method parses JSON and routes to handlers, but doesn't validate that required fields exist. For example, a malformed { t: 'join' } without roomId would pass undefined to handleJoin.
🔎 Proposed fix with field validation
handleMessage(ws: WebSocketConnection, data: string): void {
let msg: SignalMsg;
try {
msg = JSON.parse(data);
} catch {
this.send(ws, { t: 'error', message: 'Invalid JSON' });
return;
}
+ if (!msg || typeof msg.t !== 'string') {
+ this.send(ws, { t: 'error', message: 'Invalid message format' });
+ return;
+ }
switch (msg.t) {
case 'join':
+ if (typeof msg.roomId !== 'string') {
+ this.send(ws, { t: 'error', message: 'Missing roomId' });
+ return;
+ }
this.handleJoin(ws, msg.roomId);
break;
case 'sdp':
+ if (!msg.description) {
+ this.send(ws, { t: 'error', message: 'Missing description' });
+ return;
+ }
this.handleSDP(ws, msg.to, msg.description);
break;
case 'ice':
+ if (!msg.candidate) {
+ this.send(ws, { t: 'error', message: 'Missing candidate' });
+ return;
+ }
this.handleICE(ws, msg.to, msg.candidate);
break;🤖 Prompt for AI Agents
In packages/runtime/src/webrtc-signaling.ts around lines 189 to 214, the parsed
SignalMsg is routed without validating required fields so handlers may receive
undefined (e.g., {t:'join'} missing roomId). Before switching or before calling
each handler, validate required properties per message type (join requires a
non-empty roomId string, sdp requires a valid "to" id and "description"
object/string, ice requires "to" and "candidate"), and if validation fails send
an error via this.send(ws, { t: 'error', message: '...' }) and return; ensure
types are checked (typeof/Array.isArray/null checks where appropriate) and avoid
calling handlers with undefined fields.
- Add shared WebRTC types to @agentuity/core (SignalMessage, SDPDescription, ICECandidate, WebRTCConnectionState, WebRTCDisconnectReason, WebRTCSignalingCallbacks) - Refactor frontend WebRTCManager with explicit state machine (idle → connecting → signaling → negotiating → connected) - Add WebRTCClientCallbacks interface for SDK users to hook into all lifecycle events - Add WebRTCSignalingCallbacks to backend for room/peer event monitoring - Update React hook to expose new state and forward user callbacks - Add 7 new tests for backend callback functionality BREAKING CHANGE: WebRTCManagerState now has 'state' property (WebRTCConnectionState). 'status' is deprecated but still available for backwards compatibility. Amp-Thread-ID: https://ampcode.com/threads/T-019b4261-8e29-754e-a659-41f5d637485c Co-authored-by: Amp <amp@ampcode.com>
Summary
Adds WebRTC support across the Agentuity SDK for real-time peer-to-peer communication.
Packages Updated
WebRTCRoomManager)WebRTCManagerclass with perfect negotiationuseWebRTCCallhook for React applicationsFeatures
TODOs
Make WebRTC More Generic
WebRTCManagerwork withoutgetUserMedia(data-only mode)WebRTCSignaling+WebRTCMedia+WebRTCDatacomponentsMultiplayer / Non-Audio-Video Use Cases
Integration Testing
Other Improvements
Summary by CodeRabbit
New Features
useWebRTCCallReact hook for building video chat applicationsDocumentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.