Skip to content

Conversation

@potofpie
Copy link
Member

@potofpie potofpie commented Dec 21, 2025

Summary

Adds WebRTC support across the Agentuity SDK for real-time peer-to-peer communication.

Packages Updated

  • @agentuity/runtime: WebRTC signaling server with room management (WebRTCRoomManager)
  • @agentuity/frontend: Framework-agnostic WebRTCManager class with perfect negotiation
  • @agentuity/react: useWebRTCCall hook for React applications
  • CLI: Route helper for WebRTC signaling endpoints

Features

  • Room-based peer management with configurable max peers
  • Perfect negotiation pattern for reliable connection establishment
  • ICE candidate buffering until remote description is set
  • Audio/video mute controls
  • Auto-reconnection handling

TODOs

Make WebRTC More Generic

  • Separate media handling from signaling/data channel infrastructure
  • Add data channel support without requiring media streams
  • Make WebRTCManager work without getUserMedia (data-only mode)
  • Consider splitting into WebRTCSignaling + WebRTCMedia + WebRTCData components
  • Abstract track/stream handling to support custom track sources

Multiplayer / Non-Audio-Video Use Cases

  • Add data channel creation and message passing API
  • Support for game state synchronization via data channels
  • Low-latency binary data transfer (ArrayBuffer support)
  • Multi-peer mesh networking (beyond 2 peers)
  • Consider SFU architecture for larger rooms

Integration Testing

  • Test signaling server with mocked WebSocket connections
  • End-to-end test with headless browser (Playwright)
  • Test room join/leave scenarios
  • Test peer reconnection after disconnect
  • Test ICE candidate exchange timing edge cases
  • Test offer/answer collision (perfect negotiation)
  • Test with simulated network conditions (latency, packet loss)

Other Improvements

  • Add TURN server configuration guidance
  • Connection quality metrics/stats API
  • Screen sharing support
  • Recording support

Summary by CodeRabbit

  • New Features

    • Added WebRTC video calling capability with peer signaling support
    • Introduced useWebRTCCall React hook for building video chat applications
    • Added sample WebRTC video chat project demonstrating integration patterns
  • Documentation

    • Added comprehensive guides for WebRTC setup and usage
    • Included API documentation for signaling and connection management
  • Tests

    • Added WebRTC signaling test suite

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Introduces 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

Cohort / File(s) Summary
webrtc-test app configuration
apps/testing/webrtc-test/.gitignore, apps/testing/webrtc-test/.vscode/settings.json, apps/testing/webrtc-test/tsconfig.json, apps/testing/webrtc-test/agentuity.config.ts, apps/testing/webrtc-test/agentuity.json, apps/testing/webrtc-test/package.json
Scaffolds project configuration: ignore rules, VSCode settings, TypeScript compilation with path aliases, Agentuity config with dev workbench, CLI schema, and package metadata with Bun-based scripts.
webrtc-test app core
apps/testing/webrtc-test/app.ts
Defines application entry point using Agentuity runtime with setup/shutdown lifecycle and server logging.
webrtc-test documentation
apps/testing/webrtc-test/README.md, apps/testing/webrtc-test/AGENTS.md, apps/testing/webrtc-test/src/agent/AGENTS.md, apps/testing/webrtc-test/src/api/AGENTS.md, apps/testing/webrtc-test/src/web/AGENTS.md
Comprehensive guides covering project overview, agent/API/web folder structures, development workflow, and usage patterns with code examples.
webrtc-test agent implementation
apps/testing/webrtc-test/src/agent/hello/agent.ts, apps/testing/webrtc-test/src/agent/hello/index.ts
Implements example hello agent with schema-validated input/output and greeting handler.
webrtc-test API routes
apps/testing/webrtc-test/src/api/index.ts
Defines POST /hello endpoint with agent invocation and registers WebRTC signaling endpoint via api.webrtc('/call').
webrtc-test React UI
apps/testing/webrtc-test/src/web/App.tsx, apps/testing/webrtc-test/src/web/frontend.tsx, apps/testing/webrtc-test/src/web/index.html
Implements React video call interface with useWebRTCCall hook, stream attachment, join/leave flows, audio/video controls, and HMR support.
CLI build/dev updates
packages/cli/src/cmd/build/ast.ts, packages/cli/src/cmd/dev/index.ts
Adds webrtc routing case (treats as WebSocket GET with signal suffix); reorders dev build to generate metadata before backend sync.
Frontend WebRTC manager
packages/frontend/src/webrtc-manager.ts, packages/frontend/src/index.ts
Introduces framework-agnostic WebRTCManager class for peer connection lifecycle, media stream management, perfect negotiation signaling, ICE handling, audio/video muting, and cleanup; exports public API.
React WebRTC hook
packages/react/src/webrtc.tsx, packages/react/src/index.ts
Provides useWebRTCCall hook integrating WebRTCManager with React lifecycle, state management, and media controls; re-exports WebRTC types.
Runtime WebRTC signaling
packages/runtime/src/webrtc-signaling.ts, packages/runtime/src/index.ts, packages/runtime/src/router.ts
Adds server-side WebRTCRoomManager for in-memory room/peer management, join/disconnect handling, and SDP/ICE relaying; extends router with webrtc() builder method; exports public API.
WebRTC signaling tests
packages/runtime/test/webrtc-signaling.test.ts
Comprehensive test suite covering join flows, disconnect cleanup, SDP/ICE relaying, message routing, room stats, and maxPeers enforcement.

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')
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • packages/frontend/src/webrtc-manager.ts — Dense WebRTC protocol logic including perfect negotiation state machine, ICE candidate buffering, track attachment, media stream lifecycle, and error handling across multiple asynchronous flows
  • packages/runtime/src/webrtc-signaling.ts — Server-side state management with room/peer tracking, message routing, and capacity enforcement; verify room cleanup, message validation, and peer isolation
  • packages/react/src/webrtc.tsx — React lifecycle integration with WebRTCManager, state synchronization, cleanup on unmount, and proper handling of mute state and callbacks
  • apps/testing/webrtc-test/src/web/App.tsx — Video stream ref attachment timing, cleanup order, responsive stream rendering, and state consistency during connection transitions
  • packages/runtime/test/webrtc-signaling.test.ts — Large test suite; verify mock WebSocket fidelity, message parsing edge cases, and room cleanup scenarios
  • packages/cli/src/cmd/dev/index.ts — Reordered build flow; confirm metadata generation and backend sync ordering does not break existing workflows

Poem

🐰 Hops of joy! WebRTC calls now bloom,
Peers connect through signals in the room,
Video streams flow both far and near,
React hooks simplify the engineer,
From browser to server, signaling's clear! 🎥✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding WebRTC signaling and peer connection support, which aligns with the comprehensive feature set across multiple packages.

Comment @coderabbitai help to get the list of available commands and usage tips.

@potofpie
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a 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 srcDir which 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/bun version for reproducible builds.

Using "latest" for @types/bun may 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 WebRTCRoomManager class doesn't use logging, context-dependent features, or any agent utilities—its methods only manage room state and relay WebSocket messages. However, since the regular websocket() implementation captures AgentContext for consistency and future flexibility, capturing context in webrtc() 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., text or plaintext) 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., text or plaintext) 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: Use substring instead of deprecated substr.

The example code uses substr which is deprecated. Consider updating to substring for 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 text or plaintext would 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 handleJoin function doesn't validate that roomId is non-empty before calling connect(). 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?.baseUrl contains an invalid URL that doesn't match the regex pattern, wsBase could produce unexpected results. The current implementation assumes well-formed HTTP(S) URLs.


121-160: ESLint disable comment masks a potential stale closure issue.

The useMemo creates callbacks that capture localVideoRef and remoteVideoRef refs, 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 managerOptions changes (e.g., options.media reference 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 for media constraints.

Consider documenting this behavior or using useRef to 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 in hangup callback.

The WebRTCManager.hangup() method already triggers setStatus('disconnected') via the onStatusChange callback configured in managerOptions. Setting state again here creates duplicate renders.

However, the explicit setPeerId(null) and setRemotePeerId(null) are needed since those aren't cleared by the manager's callback. Consider removing only the redundant setStatus:

 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 validating roomId input.

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 to is 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 on peerId could throw if called before join.

this.peerId! assumes the peer ID is set, but onicecandidate could theoretically fire before the joined message 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

📥 Commits

Reviewing files that changed from the base of the PR and between 93f1c58 and 26f854e.

⛔ Files ignored due to path filters (4)
  • apps/testing/integration-suite/src/generated/app.ts is excluded by !**/generated/**
  • apps/testing/webrtc-test/src/generated/app.ts is excluded by !**/generated/**
  • apps/testing/webrtc-test/src/web/public/favicon.ico is excluded by !**/*.ico
  • bun.lock is 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.ts
  • apps/testing/webrtc-test/src/agent/hello/index.ts
  • packages/cli/src/cmd/build/ast.ts
  • apps/testing/webrtc-test/agentuity.config.ts
  • packages/runtime/src/index.ts
  • packages/frontend/src/index.ts
  • apps/testing/webrtc-test/src/web/App.tsx
  • packages/react/src/webrtc.tsx
  • apps/testing/webrtc-test/package.json
  • apps/testing/webrtc-test/src/web/frontend.tsx
  • apps/testing/webrtc-test/agentuity.json
  • apps/testing/webrtc-test/src/api/index.ts
  • apps/testing/webrtc-test/app.ts
  • packages/runtime/src/webrtc-signaling.ts
  • packages/runtime/test/webrtc-signaling.test.ts
  • packages/frontend/src/webrtc-manager.ts
  • packages/react/src/index.ts
  • apps/testing/webrtc-test/tsconfig.json
  • packages/runtime/src/router.ts
  • packages/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.ts
  • apps/testing/webrtc-test/src/agent/hello/index.ts
  • packages/cli/src/cmd/build/ast.ts
  • apps/testing/webrtc-test/agentuity.config.ts
  • packages/runtime/src/index.ts
  • packages/frontend/src/index.ts
  • apps/testing/webrtc-test/src/web/App.tsx
  • packages/react/src/webrtc.tsx
  • apps/testing/webrtc-test/src/web/frontend.tsx
  • apps/testing/webrtc-test/src/api/index.ts
  • apps/testing/webrtc-test/app.ts
  • packages/runtime/src/webrtc-signaling.ts
  • packages/runtime/test/webrtc-signaling.test.ts
  • packages/frontend/src/webrtc-manager.ts
  • packages/react/src/index.ts
  • packages/runtime/src/router.ts
  • packages/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 of existsSync(f))
Use async/await for all I/O operations
All code must be TypeScript with strict mode

Files:

  • packages/cli/src/cmd/build/ast.ts
  • packages/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 use any for type safety
Use tui.* helpers for formatted output without log prefixes (success, error, warning, info, bold, muted, link)
Use ctx.logger for standard logging with prefixes (fatal, error, info, debug), use logger.fatal() to log error and exit with code 1 instead of logger.error() + process.exit(1)
Follow commander.js best practices for command parsing and routing
Subcommands are defined in separate files and exported as SubcommandDefinition
Commands must validate their own config requirements
Use tui.success(), tui.error(), tui.warning(), tui.info() for semantic messages in command output
Use tui.bold(), tui.muted(), tui.link() for text formatting in command output

Files:

  • packages/cli/src/cmd/build/ast.ts
  • packages/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.ts
  • packages/runtime/src/index.ts
  • packages/frontend/src/index.ts
  • packages/runtime/src/webrtc-signaling.ts
  • packages/frontend/src/webrtc-manager.ts
  • packages/react/src/index.ts
  • packages/runtime/src/router.ts
  • packages/cli/src/cmd/dev/index.ts
packages/runtime/src/**/*.ts

📄 CodeRabbit inference engine (packages/runtime/AGENTS.md)

packages/runtime/src/**/*.ts: Use ctx.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.ts
  • packages/runtime/src/webrtc-signaling.ts
  • packages/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.ts
  • packages/frontend/src/index.ts
  • packages/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.ts
  • packages/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.ts
  • packages/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.tsx
  • packages/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.tsx
  • packages/react/src/index.ts
packages/runtime/test/**/*.test.ts

📄 CodeRabbit inference engine (packages/runtime/AGENTS.md)

packages/runtime/test/**/*.test.ts: Use app.request() for testing routes instead of testClient() 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: Use createRouter() to create Hono router instances with extended methods (get, post, put, delete, patch, stream, websocket, sse)
Validate route input using agent.validator() with automatic type inference from agent schema, or override with custom schemas
Use router.websocket() for WebSocket routes and router.sse() for Server-Sent Events routes
Native Bun WebSocket support via hono/bun must 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 an index.ts file containing a CommandDefinition

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.ts
  • apps/testing/webrtc-test/src/agent/hello/index.ts
  • packages/cli/src/cmd/build/ast.ts
  • apps/testing/webrtc-test/agentuity.config.ts
  • packages/runtime/src/index.ts
  • packages/frontend/src/index.ts
  • apps/testing/webrtc-test/src/api/index.ts
  • apps/testing/webrtc-test/app.ts
  • packages/runtime/src/webrtc-signaling.ts
  • packages/runtime/test/webrtc-signaling.test.ts
  • packages/frontend/src/webrtc-manager.ts
  • packages/react/src/index.ts
  • packages/runtime/src/router.ts
  • packages/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.ts
  • packages/runtime/src/webrtc-signaling.ts
  • packages/runtime/test/webrtc-signaling.test.ts
  • packages/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 /signal to the path suffix. The logic is consistent with similar routing methods like stream, sse, and websocket.

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 createApp pattern. 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 WebRTCRoomManager and WebRTCOptions for 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.data for 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 /hello endpoint and clean WebRTC signaling integration via api.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 parseMessage utility. 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 from field, and error handling for peers not in a room.


235-323: LGTM!

Message parsing, routing, error handling, room stats, and custom maxPeers configuration are all well-tested. The test suite provides good coverage of the WebRTCRoomManager functionality.

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 WebRTCManagerOptions while adding React-specific autoConnect option.


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 t field provides clean message type discrimination for the switch statement in handleMessage.


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 WebSocketConnection interface defined in packages/runtime/src/router.ts only exposes onOpen, onMessage, onClose, and send methods. The proposed fix references ws.readyState, which does not exist on this interface and would not compile.

While the client-side WebSocketManager does check ws.readyState === WebSocket.OPEN before sending, the server-side WebSocketConnection interface 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 SignalMsg uses RTCSessionDescriptionInit and RTCIceCandidateInit (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 onnegotiationneeded handler properly sets makingOffer flag, creates offer via setLocalDescription() (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.warn is acceptable here per coding guidelines.

The coding guidelines specify ctx.logger for runtime code, but this is the frontend package which should use standard browser APIs. console.warn is 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 to hangup() for full cleanup. This provides a clear API for React's useEffect cleanup.

Comment on lines +19 to +20
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]\*.json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Glob patterns appear malformed.

  • Line 19: _.log will 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]\*.json has 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.

Suggested change
_.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.

Comment on lines +24 to +25
.env
.env.\*
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
.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.

Comment on lines +33 to +60
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>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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).

Comment on lines +152 to +191
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>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines 142 to 179
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)));
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines 161 to 164
this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data) as SignalMsg;
this.handleSignalingMessage(msg);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines 189 to 214
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}`,
});
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants