Skip to content

feat: Add WebRTC signaling and peer connection support#285

Merged
jhaynie merged 24 commits intomainfrom
webrtc
Feb 6, 2026
Merged

feat: Add WebRTC signaling and peer connection support#285
jhaynie merged 24 commits intomainfrom
webrtc

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

    • End-to-end WebRTC: server signaling, in-memory room manager, client manager, React hook, and sample UIs for calls and testing
    • Data channels: messaging, cursor sharing, screen sharing, recording, and media controls
  • Documentation

    • Detailed guides for agents, APIs, web frontend and a WebRTC example app; README updates
  • Tests

    • Comprehensive unit tests and Playwright E2E WebRTC suites
  • Chores

    • New webrtc-test project, editor settings, gitignore, configs, and CI/test runner adjustments

@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

📝 Walkthrough

Walkthrough

Adds end-to-end WebRTC: server signaling and router integration, an in-memory room manager, frontend WebRTC manager, React hook and UI components, unit and Playwright E2E tests, a webrtc-test app scaffold with docs, and CI/playwright config updates.

Changes

Cohort / File(s) Summary
webrtc-test: project scaffold & runtime
apps/testing/webrtc-test/.gitignore, apps/testing/webrtc-test/.vscode/settings.json, apps/testing/webrtc-test/agentuity.config.ts, apps/testing/webrtc-test/agentuity.json, apps/testing/webrtc-test/package.json, apps/testing/webrtc-test/tsconfig.json, apps/testing/webrtc-test/app.ts
New Agentuity/Bun TypeScript app scaffold: gitignore, VSCode settings, agentuity config/manifest, package manifest, tsconfig, and runtime entrypoint.
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
Adds comprehensive docs for agents, APIs, web frontend patterns, build/runtime behavior, and examples.
webrtc-test: example agent & API
apps/testing/webrtc-test/src/agent/hello/agent.ts, apps/testing/webrtc-test/src/agent/hello/index.ts, apps/testing/webrtc-test/src/api/index.ts
Adds example "hello" agent and API router; registers POST /hello and mounts a WebRTC signaling route.
webrtc-test: web frontend
apps/testing/webrtc-test/src/web/index.html, apps/testing/webrtc-test/src/web/frontend.tsx, apps/testing/webrtc-test/src/web/App.tsx
Adds React entry, HMR-aware bootstrap, and App implementing a WebRTC call UI with stream mounting, mute controls, join/hangup flows and inline styles.
frontend package: WebRTC manager & exports
packages/frontend/src/webrtc-manager.ts, packages/frontend/src/index.ts
Introduces WebRTCManager class, related types (status, callbacks, options, state), media/control APIs (getState/getLocalStream/getRemoteStream/mute/hangup/dispose), and re-exports from package index.
react package: hook & re-exports
packages/react/src/webrtc.tsx, packages/react/src/index.ts
Adds useWebRTCCall React hook with types and result interface; re-exports WebRTC APIs (manager and types) from React package.
runtime package: signaling, router integration & exports
packages/runtime/src/webrtc-signaling.ts, packages/runtime/src/router.ts, packages/runtime/src/index.ts
Adds signaling data types and WebRTCRoomManager (in-memory rooms, join/sdp/ice handling, maxPeers), router.webrtc(path, options) to mount WS signaling endpoint, and re-exports for runtime API.
runtime tests: unit tests for signaling
packages/runtime/test/webrtc-signaling.test.ts
Comprehensive unit tests for WebRTCRoomManager: join/disconnect, SDP/ICE relaying, message parsing, room limits, and room stats.
E2E app: integration & UI page
apps/testing/e2e-web/.vscode/settings.json, apps/testing/e2e-web/src/api/index.ts, apps/testing/e2e-web/src/web/App.tsx, apps/testing/e2e-web/src/web/WebRTCTestPage.tsx
Adds VSCode settings, mounts a /webrtc signaling route for E2E, integrates WebRTCTestPage providing full-featured test UI (data channels, screen share, recording, cursor sharing) and route wiring.
Playwright E2E: tests & config & workflow
e2e/webrtc.pw.ts, playwright.config.ts, .github/workflows/package-smoke-test.yaml
Adds extensive Playwright WebRTC E2E tests, updates Playwright config (camera/mic permissions, fake-media args, increased CI workers), and adjusts CI workflow timeout and E2E invocation.
Monorepo docs updates
AGENTS.md, README.md
Repository-level docs updated to reference WebRTC signaling, the WebRTC manager, and React WebRTC hooks.
Misc: editor & workspace settings
apps/testing/webrtc-test/.gitignore, apps/testing/e2e-web/.vscode/settings.json
Adds ignore rules and workspace JSON schemas for agentuity.json across test apps.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


🧹 Recent nitpick comments
e2e/webrtc.pw.ts (6)

140-177: Consider using the connectPeer helper in "Two Peers" tests to reduce duplication.

All six tests in this group manually navigate, clear/fill the room ID, and click connect — exactly what connectPeer already does. Every other multi-peer test group (Multi-Peer Mesh, Screen Sharing, Reconnection, etc.) uses the helper. This inconsistency adds ~100 lines of redundant code and increases the maintenance surface if the connection flow changes.

For example, the test starting at line 140 could be simplified:

♻️ Example refactor for one test
 test('should establish peer connection between two browsers', async () => {
   const roomId = `test-room-${Date.now()}`;
-
-  // Navigate both pages
-  await Promise.all([page1.goto('/webrtc'), page2.goto('/webrtc')]);
-  await Promise.all([waitForPageLoad(page1), waitForPageLoad(page2)]);
-
-  // Set same room ID for both
-  await page1.getByTestId('room-id-input').clear();
-  await page1.getByTestId('room-id-input').fill(roomId);
-  await page2.getByTestId('room-id-input').clear();
-  await page2.getByTestId('room-id-input').fill(roomId);
-
-  // Connect first peer
-  await page1.getByTestId('connect-btn').click();
-  await expect(page1.getByTestId('connection-state')).toContainText('signaling', {
-    timeout: 5000,
-  });
-
-  // Connect second peer
-  await page2.getByTestId('connect-btn').click();
+  await connectPeer(page1, roomId);
+  await expect(page1.getByTestId('connection-state')).toContainText('signaling', {
+    timeout: 5000,
+  });
+  await connectPeer(page2, roomId);

   // Both should eventually reach connected state
   ...
 });

Also applies to: 179-210, 212-259, 261-290, 292-326, 328-369


426-429: Hardcoded waitForTimeout calls may cause CI flakiness.

There are ~9 instances of waitForTimeout() with arbitrary delays (200ms–2000ms). These are inherently racy: too short and the test flakes; too long and the suite slows down. Playwright recommends replacing fixed waits with condition-based waits (expect(...).toContainText(...), waitForSelector, waitForFunction, etc.) wherever possible.

Some of these are harder to replace (e.g., line 573 waiting for recording data to accumulate, lines 428/474 delaying mesh formation), but others like line 777 (verifying state persistence) and lines 683/687 (cursor data propagation) could be replaced with explicit state assertions.

Not blocking, but worth addressing incrementally to improve reliability.

Also applies to: 472-475, 492-493, 573-573, 682-688, 777-777, 1151-1151, 1184-1184


657-695: Cursor tracking test doesn't verify remote peer received cursor data.

The test is titled "should send cursor position on mouse move" but only verifies that both canvases remain visible after mouse movements. It doesn't assert that peer2 actually received cursor position data (e.g., by checking a rendered cursor element or a data-channel message on peer2's side). Consider adding an assertion that validates the cursor data was received, such as checking for a remote cursor indicator or a cursor-related message in peer2's messages.


1275-1281: Timestamp tolerance is reasonable but could be tightened.

The + 1000 ms tolerance (line 1281) is generous. In a local E2E environment the clock skew should be negligible since both pages run in the same browser. Consider reducing to + 200 or so to catch regressions where the timestamp is incorrectly assigned. Not critical — just a nit.


1302-1303: Repeated "My Peer ID: " prefix parsing — extract a small helper.

The pattern innerText() → replace('My Peer ID: ', '').trim() appears in multiple tests (lines 1302–1303, 1339–1342). A tiny utility like getPeerId(page) would reduce duplication and protect against UI text changes.

♻️ Suggested helper
async function getPeerId(page: Page): Promise<string> {
	const text = await page.getByTestId('peer-id').innerText();
	return text.replace('My Peer ID: ', '').trim();
}

Also applies to: 1339-1342


65-65: Consider splitting this 1400-line test file into feature-focused files.

The file covers 16 distinct test groups (Data Channels, Multi-Peer Mesh, Screen Sharing, Recording, Reconnection, Cursor Tracking, Media Controls, Error Handling, Media Stream Assignment, Peer List Updates, State Cleanup, State Transitions, Recording Metadata, Message Metadata, Connection Info, Data Channel State). Splitting into ~4–5 files (e.g., webrtc-data-channels.pw.ts, webrtc-media.pw.ts, webrtc-reconnection.pw.ts, webrtc-state.pw.ts) with shared helpers in a webrtc-helpers.ts would improve navigability and allow parallel test execution per file.

Also applies to: 373-373, 512-512, 562-562, 586-586, 640-640, 706-706, 786-786, 810-810, 872-872, 961-961, 1093-1093, 1136-1136, 1203-1203, 1288-1288, 1362-1362

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5e9278 and 04c8c32.

📒 Files selected for processing (1)
  • e2e/webrtc.pw.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Prettier formatter with tabs (width 3), single quotes, and semicolons for TypeScript files
Use TypeScript strict mode with ESNext target and bundler moduleResolution
Use StructuredError from @agentuity/core for error handling

Files:

  • e2e/webrtc.pw.ts
🧠 Learnings (1)
📚 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:

  • e2e/webrtc.pw.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Framework Integration Tests (TanStack & Next.js)
  • GitHub Check: Template Integration Tests
  • GitHub Check: SDK Integration Test Suite
  • GitHub Check: Playwright E2E Smoke Test
  • GitHub Check: Cloud Deployment Tests
  • GitHub Check: Package Installation & Usage Test
  • GitHub Check: Sandbox CLI Tests
  • GitHub Check: Build
🔇 Additional comments (5)
e2e/webrtc.pw.ts (5)

1-5: LGTM!

Import set is clean and the waitForPageLoad helper is a good pattern for ensuring page readiness.


7-28: LGTM — Good getDisplayMedia polyfill for headless testing.

The fallback-to-getUserMedia approach is a practical workaround for headless Chromium, which doesn't support getDisplayMedia natively. The two-stage check (missing → create, existing → wrap with try/catch fallback) is correct.


30-63: LGTM — Well-structured connectPeer helper.

Explicit handling of both true and false for enableVideo/enableAudio checkboxes ensures deterministic state regardless of page defaults. Clean design.


555-555: Good fix — regex-based JSON assertion addresses the previous review feedback.

Using /"active"\s*:\s*false/ correctly tolerates whitespace variations in JSON formatting.


800-803: Good use of regex for flexible state matching.

/idle|error/ correctly handles the non-deterministic final state when auto-reconnect is disabled and the signal URL is invalid.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

potofpie and others added 5 commits December 21, 2025 14:35
- 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>
- Resolve merge conflicts with main branch
- Create new webrtc() middleware in packages/runtime/src/handlers/webrtc.ts
- Deprecate router.webrtc() with migration error message
- Export webrtc middleware from @agentuity/runtime
- Convert webrtc-test sample project to use new middleware pattern
- Add missing dependencies to webrtc-test (core, frontend, hono)
- Fix lint errors in webrtc-manager.ts and webrtc.tsx

Amp-Thread-ID: https://ampcode.com/threads/T-019bc897-cc55-7519-9406-bbc60bd52781
Co-authored-by: Amp <amp@ampcode.com>
@github-actions
Copy link

github-actions bot commented Jan 16, 2026

📦 Canary Packages Published

version: 1.0.1-228d79d

Packages
Package Version URL
@agentuity/core 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-core-1.0.1-228d79d.tgz
@agentuity/server 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-server-1.0.1-228d79d.tgz
@agentuity/schema 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-schema-1.0.1-228d79d.tgz
@agentuity/auth 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-auth-1.0.1-228d79d.tgz
@agentuity/react 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-react-1.0.1-228d79d.tgz
@agentuity/postgres 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-postgres-1.0.1-228d79d.tgz
@agentuity/workbench 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-workbench-1.0.1-228d79d.tgz
@agentuity/cli 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-cli-1.0.1-228d79d.tgz
@agentuity/frontend 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-frontend-1.0.1-228d79d.tgz
@agentuity/evals 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-evals-1.0.1-228d79d.tgz
@agentuity/drizzle 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-drizzle-1.0.1-228d79d.tgz
@agentuity/runtime 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-runtime-1.0.1-228d79d.tgz
@agentuity/opencode 1.0.1-228d79d https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-opencode-1.0.1-228d79d.tgz
Install

Add to your package.json:

{
  "dependencies": {
    "@agentuity/core": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-core-1.0.1-228d79d.tgz",
    "@agentuity/server": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-server-1.0.1-228d79d.tgz",
    "@agentuity/schema": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-schema-1.0.1-228d79d.tgz",
    "@agentuity/auth": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-auth-1.0.1-228d79d.tgz",
    "@agentuity/react": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-react-1.0.1-228d79d.tgz",
    "@agentuity/postgres": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-postgres-1.0.1-228d79d.tgz",
    "@agentuity/workbench": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-workbench-1.0.1-228d79d.tgz",
    "@agentuity/cli": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-cli-1.0.1-228d79d.tgz",
    "@agentuity/frontend": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-frontend-1.0.1-228d79d.tgz",
    "@agentuity/evals": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-evals-1.0.1-228d79d.tgz",
    "@agentuity/drizzle": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-drizzle-1.0.1-228d79d.tgz",
    "@agentuity/runtime": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-runtime-1.0.1-228d79d.tgz",
    "@agentuity/opencode": "https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-opencode-1.0.1-228d79d.tgz"
  }
}

Or install directly:

bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-core-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-server-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-schema-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-auth-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-react-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-postgres-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-workbench-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-cli-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-frontend-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-evals-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-drizzle-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-runtime-1.0.1-228d79d.tgz
bun add https://agentuity-sdk-objects.t3.storageapi.dev/npm/1.0.1-228d79d/agentuity-opencode-1.0.1-228d79d.tgz

potofpie and others added 8 commits January 19, 2026 11:29
# Conflicts:
#	bun.lock
#	packages/cli/src/cmd/cloud/env/delete.ts
#	packages/opencode/src/types.ts
- Fix .gitignore glob patterns (*.log instead of _.log, .env.* instead of .env.\*)
- Fix AgentuityProvider placement in AGENTS.md examples (hooks must be inside provider)
- Add try/catch for JSON.parse in WebSocket message handler
- Add cleanup of local media tracks on connection error
- Add validation for required message fields in WebRTC signaling
- Add guard for undefined screenTrack in startScreenShare
- Fix CURSOR_COLORS array index type safety
Production hardening:
- Auto-reconnection with exponential backoff (1s, 2s, 4s... max 30s)
- maxReconnectAttempts option (default: 5)
- Reconnection callbacks: onReconnecting, onReconnected, onReconnectFailed
- autoReconnect option (default: true)
- Connection timeout detection (30s default)
- ICE gathering timeout warnings (10s default)

Test coverage:
- Multi-peer E2E tests (3-4 peers)
- Screen sharing tests
- Recording tests
- Reconnection tests
- Stats API unit tests

Documentation:
- Updated TURN server configuration guide with free tier examples
- Added SFU scaling guide (mesh vs SFU decision matrix)
- Updated PR description with completed TODOs
- Fix memory leak by nulling RTCPeerConnection handlers before close
- Prevent race condition with isConnecting guard
- Handle async errors in signaling message handler
- Add timeout to stopAllRecordings to prevent hanging
- Document data channel JSON auto-parsing behavior
The agentuity.json file causes the dev server to require authentication,
which breaks CI. E2E test apps should run without auth in local mode.
@blacksmith-sh

This comment has been minimized.

jhaynie and others added 3 commits February 5, 2026 17:50
The test 'should stop after max reconnect attempts' was failing because
page.route() only blocks HTTP requests, not WebSocket connections.

Changed approach: set an invalid signal URL before forcing WS close so
that reconnection attempts fail at the WebSocket connection level.
- Update README.md and AGENTS.md with WebRTC feature descriptions
- Add WebRTC section to packages/frontend/README.md
- Add useWebRTC hook section to packages/react/README.md
- Create packages/workbench/README.md
- Remove deprecated router.webrtc() stub (never released)
- Remove deprecated SignalMsg type alias (never released)
@jhaynie jhaynie marked this pull request as ready for review February 6, 2026 03:13
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: 2

🤖 Fix all issues with AI agents
In `@apps/testing/e2e-web/src/web/WebRTCTestPage.tsx`:
- Around line 290-298: stopRecording currently awaits
recordingHandleRef.current.stop() without handling rejections; wrap the await in
a try/catch similar to startScreenShare/stopScreenShare to catch errors from
handle.stop(), log or call the existing logger (or console.error) with the
error, and ensure you still clear recordingHandleRef.current and update state
(setRecordingState and setRecordingSize/setRecordingMimeType as appropriate) in
both success and error paths to avoid leaving the ref/state inconsistent. Use
the existing recordingHandleRef, stopRecording, setRecordingState,
setRecordingSize, and setRecordingMimeType symbols to make the changes.
- Around line 230-237: The interval created as checkPeerId leaks if peerId never
arrives or connect() is called again; create a ref (e.g. checkPeerIdRef via
useRef<ReturnType<typeof setInterval> | null>) to store the interval ID when you
call setInterval in the connect() flow (where checkPeerId is currently
declared), clear any existing interval at the start of connect() before creating
a new one, and clear the refged interval inside disconnect() and the component
cleanup effect so clearInterval(checkPeerIdRef.current) is always called and
checkPeerIdRef.current is then set to null; keep the existing logic that sets
setPeerId(managerState.peerId) and clears the interval when peerId is found.
🧹 Nitpick comments (1)
apps/testing/e2e-web/src/web/WebRTCTestPage.tsx (1)

300-311: Test utilities rely on private internals via unsafe casts.

The as unknown as { ws?: WebSocket } and as unknown as { options?: ... } casts bypass type safety to access private properties. If WebRTCManager's internals are renamed or restructured, these will silently do nothing. Consider exposing test-only helper methods on WebRTCManager (e.g., _forceClose(), _setSignalUrl()) or using a test subclass, so breakage surfaces at compile time rather than as a silent test failure.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26f854e and 041af70.

⛔ Files ignored due to path filters (1)
  • apps/testing/e2e-web/src/generated/routes.ts is excluded by !**/generated/**
📒 Files selected for processing (6)
  • AGENTS.md
  • README.md
  • apps/testing/e2e-web/.vscode/settings.json
  • apps/testing/e2e-web/src/api/index.ts
  • apps/testing/e2e-web/src/web/App.tsx
  • apps/testing/e2e-web/src/web/WebRTCTestPage.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/testing/e2e-web/.vscode/settings.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Prettier formatter with tabs (width 3), single quotes, and semicolons for TypeScript files
Use TypeScript strict mode with ESNext target and bundler moduleResolution
Use StructuredError from @agentuity/core for error handling

Files:

  • apps/testing/e2e-web/src/web/App.tsx
  • apps/testing/e2e-web/src/api/index.ts
  • apps/testing/e2e-web/src/web/WebRTCTestPage.tsx
🧠 Learnings (4)
📚 Learning: 2026-01-02T15:01:17.084Z
Learnt from: CR
Repo: agentuity/sdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T15:01:17.084Z
Learning: Use Bun workspaces monorepo structure with packages organized in `packages/` directory

Applied to files:

  • AGENTS.md
📚 Learning: 2026-01-09T16:26:51.893Z
Learnt from: jhaynie
Repo: agentuity/sdk PR: 523
File: templates/_base/src/web/frontend.tsx:13-35
Timestamp: 2026-01-09T16:26:51.893Z
Learning: In web frontend code, prefer using the built-in Error class for runtime errors. Do not throw or re-export StructuredError from agentuity/core in web app code. Replace instances of StructuredError with new Error or custom error types that extend Error; ensure error handling logic remains intact and that error messages are descriptive. This guideline applies to all web UI TypeScript/TSX files that run in the browser and import StructuredError from agentuity/core.

Applied to files:

  • apps/testing/e2e-web/src/web/App.tsx
  • apps/testing/e2e-web/src/web/WebRTCTestPage.tsx
📚 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/e2e-web/src/api/index.ts
📚 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/e2e-web/src/api/index.ts
🧬 Code graph analysis (1)
apps/testing/e2e-web/src/web/WebRTCTestPage.tsx (5)
packages/frontend/src/webrtc-manager.ts (5)
  • state (419-421)
  • setState (482-498)
  • connect (581-611)
  • startRecording (1483-1521)
  • sendJSON (1190-1192)
packages/core/src/index.ts (3)
  • WebRTCConnectionState (171-171)
  • RecordingState (180-180)
  • RecordingHandle (179-179)
packages/core/src/webrtc.ts (3)
  • WebRTCConnectionState (73-78)
  • RecordingState (185-185)
  • RecordingHandle (171-180)
packages/frontend/src/index.ts (3)
  • WebRTCConnectionState (39-39)
  • RecordingState (47-47)
  • RecordingHandle (46-46)
packages/server/src/api/api.ts (1)
  • attempt (722-727)
🔇 Additional comments (7)
AGENTS.md (1)

17-33: Package table update looks good.

Clear, concise descriptions and the expanded package list align with the monorepo structure.

README.md (1)

51-55: Clear and accurate package descriptions.
These updates succinctly surface the new WebRTC capabilities across packages.

apps/testing/e2e-web/src/api/index.ts (1)

1-7: LGTM!

Clean addition of the WebRTC signaling endpoint. The import and route registration are straightforward, and the .ts extension is correct for a route file under src/api.

apps/testing/e2e-web/src/web/App.tsx (1)

36-38: LGTM — consistent with the existing routing pattern.

The new /webrtc route follows the same early-return convention as the other routes. Note that this file has a pre-existing concern: useState/useAPI on Lines 40–41 are called after conditional early returns, which technically violates React's rules of hooks. This is not introduced by this PR, but worth noting for a future refactor (e.g., extracting the default route into its own component).

apps/testing/e2e-web/src/web/WebRTCTestPage.tsx (3)

1-3: Good import choices — no StructuredError in web frontend code.

Type-only imports for RecordingHandle and RecordingState are correct. The component correctly avoids importing StructuredError and instead uses the built-in Error class throughout (e.g., err instanceof Error checks in catch blocks). Based on learnings, web frontend code should use the built-in Error class rather than StructuredError from @agentuity/core.


71-237: connect callback stale-closure risk with enableVideo/enableAudio.

The connect callback captures enableVideo and enableAudio in its closure and lists them as dependencies, which is correct. However, all the inner callbacks (e.g., onLocalStream, onRemoteStream, onDataChannelMessage) capture the manager local variable but also reference managerRef, localVideoRef, etc. via refs — this is the right pattern and avoids stale closures. Nicely done.


393-412: disconnect cleanup is thorough.

All relevant state is reset, the manager is disposed and nulled. The only gap is the leaked checkPeerId interval addressed in the earlier comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

- stopRecording: wrap await handle.stop() in try/catch, ensure state
  is cleaned up in both success and error paths using finally block
- checkPeerId interval: store in ref (checkPeerIdRef), clear existing
  interval at start of connect(), clear in disconnect() and cleanup
  effect to prevent memory leaks if peerId never arrives
…tests

- Add 26 new tests covering cursor tracking, media controls, error handling,
  media stream assignment, peer list updates, state cleanup, state transitions,
  recording metadata, message metadata, connection info, and data channel state
- Fix flaky 3-peer mesh tests with proper timeouts and sequential connection
- Increase test coverage from ~60-70% to ~90%
- Total tests: 19 → 44
- Increase CI timeout from 10 to 15 minutes for 44 tests
- Skip redundant rebuild in CI (SDK already built by prepare-sdk-for-testing.sh)
- Increase workers from 1 to 4 in CI (tests use unique room IDs, safe to parallelize)
- Run playwright directly instead of through test-e2e.sh wrapper
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: 2

🤖 Fix all issues with AI agents
In @.github/workflows/package-smoke-test.yaml:
- Around line 349-350: The workflow removed the e2e-web build step and now runs
the "Run Playwright E2E tests" job which invokes the "bun run test:e2e" script;
confirm intent and either restore the production build or document why it's
unnecessary: if tests require a built app, add back an explicit build step that
runs "bun run build" in the e2e-web directory before "bun run test:e2e"
(mirroring the old "test:ci:e2e" behavior); if the dev server started by
"playwright.config.ts" via "bun run dev" is sufficient, add a clarifying comment
in the workflow next to the "Run Playwright E2E tests" step explaining that no
build is needed because Playwright starts the dev server and a production build
is redundant.

In `@e2e/webrtc.pw.ts`:
- Around line 912-916: The variable peer2Id is extracted via
peer2.page.getByTestId('peer-id').innerText() and assigned to peer2Id but never
used; remove the unused extraction (the const peer2IdText = ... and const
peer2Id = ... lines) or, if the peer ID is meant to be asserted/used, replace
the unused assignment by using peer2Id in the intended assertion or function
call (e.g., compare it to an expected value or pass it to the relevant method).
Locate the code around the peer2.page.getByTestId('peer-id').innerText() call
and either delete those two lines or wire peer2Id into the correct
assertion/usage.
🧹 Nitpick comments (6)
.github/workflows/package-smoke-test.yaml (1)

352-359: TODO: Artifact upload disabled since 2026-02-01 — consider re-enabling.

The comment says to re-enable after the artifact storage quota resets. Since it's now February 2026, this may be ready to turn back on. Having Playwright reports on failure is valuable for debugging flaky WebRTC E2E tests.

Would you like me to open an issue to track re-enabling the artifact upload steps across this workflow?

e2e/webrtc.pw.ts (5)

30-57: connectPeer only checks media toggles, never unchecks them.

If enableVideo or enableAudio is explicitly false, the helper silently ignores it. If the UI defaults ever change to checked, tests passing { enableVideo: false } won't actually disable video.

Consider using setChecked (like you do for auto-reconnect-toggle on line 51) for consistency:

♻️ Suggested change
-	if (options?.enableVideo) {
-		await page.getByTestId('enable-video').check();
-	}
-	if (options?.enableAudio) {
-		await page.getByTestId('enable-audio').check();
-	}
+	if (options?.enableVideo !== undefined) {
+		await page.getByTestId('enable-video').setChecked(options.enableVideo);
+	}
+	if (options?.enableAudio !== undefined) {
+		await page.getByTestId('enable-audio').setChecked(options.enableAudio);
+	}

411-455: Comments say "Peer 2" but code references peers[2] (the third peer, 0-indexed).

Lines 433 and 447 say "Peer 2 disconnects" / "Peer 2 rejoins" but operate on peers[2], which is the third peer. This is a readability trap for anyone maintaining these tests. Consider using "Peer 3" or "the third peer" in the comments to match the 0-based indexing, or switch to 1-based naming throughout.


544-548: Fragile partial-JSON assertion.

'active":false' relies on exact JSON serialization (no space after :). If the app ever switches to pretty-printed or differently-formatted JSON, this breaks silently. Prefer a looser check or parse the JSON and assert on the value.

♻️ Possible alternative
-			await expect(peer2.page.getByTestId('messages')).toContainText('active":false');
+			await expect(peer2.page.getByTestId('messages')).toContainText('active');
+			// Optionally parse and validate: extract the JSON payload and assert active === false

650-688: Cursor tracking test doesn't verify data was actually received by the remote peer.

The test is named "should send cursor position on mouse move" but only asserts that both canvases remain visible after mouse movement. It never checks that peer2 received cursor coordinates or rendered them. Consider asserting on a UI element that displays received cursor data (e.g., a data-testid="remote-cursor-position" or similar) to actually validate the feature.


569-572: Silent fallback to 0 produces a misleading assertion error.

If the regex on line 571 doesn't match (e.g., UI text format changes), the ternary falls back to 0 and the test fails with "expected 0 to be greater than 0", hiding the real problem (unexpected text format). Consider asserting the match first:

♻️ Clearer assertion
 			const sizeText = await page.getByTestId('recording-size').innerText();
 			const sizeMatch = sizeText.match(/(\d+) bytes/);
-			expect(sizeMatch ? Number(sizeMatch[1]) : 0).toBeGreaterThan(0);
+			expect(sizeMatch).toBeTruthy();
+			expect(Number(sizeMatch![1])).toBeGreaterThan(0);

This pattern is already used correctly in the similar test at lines 1191–1194.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe85a90 and 33eb8b0.

📒 Files selected for processing (3)
  • .github/workflows/package-smoke-test.yaml
  • e2e/webrtc.pw.ts
  • playwright.config.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Prettier formatter with tabs (width 3), single quotes, and semicolons for TypeScript files
Use TypeScript strict mode with ESNext target and bundler moduleResolution
Use StructuredError from @agentuity/core for error handling

Files:

  • playwright.config.ts
  • e2e/webrtc.pw.ts
🧠 Learnings (1)
📚 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:

  • playwright.config.ts
  • e2e/webrtc.pw.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Pack & Upload
  • GitHub Check: Framework Integration Tests (TanStack & Next.js)
  • GitHub Check: Cloud Deployment Tests
  • GitHub Check: Sandbox CLI Tests
  • GitHub Check: Template Integration Tests
  • GitHub Check: Package Installation & Usage Test
  • GitHub Check: Playwright E2E Smoke Test
  • GitHub Check: SDK Integration Test Suite
  • GitHub Check: Build
🔇 Additional comments (5)
playwright.config.ts (2)

12-12: LGTM — worker count increase is well-justified.

Matching workers to vCPU count with unique room IDs ensuring test isolation is a solid approach. The inline comment documenting the rationale is appreciated.


14-21: Correct setup for headless WebRTC testing.

The fake media stream flags and permission grants are the standard approach for Chromium-based WebRTC E2E tests. Since all tests currently target only Chromium (line 24), placing launchOptions.args at the top-level use block is fine.

Worth noting: these are Chromium-specific args and permissions. If Firefox or WebKit projects are added later, consider moving these into the chromium project block to avoid unexpected behavior. Not an issue today.

.github/workflows/package-smoke-test.yaml (1)

329-329: Timeout increase looks reasonable.

With the test count growing from 19 to 44 and the addition of WebRTC E2E tests, bumping from 10 to 15 minutes is a sensible adjustment.

e2e/webrtc.pw.ts (2)

1-5: LGTM on helper setup and imports.

Clean helper structure with waitForPageLoad, createPeer, and connectPeer. The addInitScript stub for getDisplayMedia with a graceful fallback is well done.


59-365: Comprehensive test suite with solid resource management.

The overall coverage is impressive — signaling, data channels, media controls, screen sharing, recording, reconnection, state cleanup, and multi-peer mesh are all exercised. The consistent use of try/finally for context cleanup and beforeEach/afterEach in the Two Peers group ensures resources are released even on failure. Timeouts are reasonable and scale appropriately for more complex scenarios (e.g., 20s for mesh formation).

Also applies to: 367-504, 506-553, 555-577, 579-631, 633-697, 699-777, 779-802, 804-864, 866-957, 959-1089, 1091-1132, 1134-1199, 1201-1284, 1286-1358, 1360-1402

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

- Remove unused peer2Id variable extraction in 'should clear remote peer ID' test
- Add comment explaining why no explicit build step is needed (dev server compiles on-the-fly)
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: 2

🤖 Fix all issues with AI agents
In `@e2e/webrtc.pw.ts`:
- Around line 544-548: The assertion checking for the literal substring
'"active":false' is fragile; update the check on
peer2.page.getByTestId('messages') to be resilient to formatting changes by
either parsing the messages text as JSON and asserting the active field equals
false, or use a regex match that tolerates whitespace (e.g., match
/"active"\s*:\s*false/ against the innerText). Locate the assertion using
peer2.page.getByTestId('messages') and replace the
toContainText('"active":false') check accordingly so it reliably verifies the
active flag regardless of JSON formatting.
- Around line 433-451: The comments wrongly label peers[2] as "Peer 2" (it's the
third peer), which is confusing; update the comment text to correctly reference
the third peer or use the same indexing language as the code (e.g., "Peer 3" or
"peers[2]") for the disconnect/rejoin steps that call
peers[2].page.getByTestId('disconnect-btn') and
peers[2].page.getByTestId('connect-btn'), and similarly adjust any other comment
lines mentioning "Peer 2" in this block so they match the code's zero-based
index.
🧹 Nitpick comments (3)
e2e/webrtc.pw.ts (3)

44-49: connectPeer won't explicitly uncheck video/audio if passed false.

If the page defaults ever change to have video/audio checkboxes pre-checked, calling connectPeer(page, roomId, { enableVideo: false }) would silently leave them checked. Consider handling the false case explicitly for symmetry.

Suggested fix
-	if (options?.enableVideo) {
-		await page.getByTestId('enable-video').check();
-	}
-	if (options?.enableAudio) {
-		await page.getByTestId('enable-audio').check();
-	}
+	if (options?.enableVideo !== undefined) {
+		await page.getByTestId('enable-video').setChecked(options.enableVideo);
+	}
+	if (options?.enableAudio !== undefined) {
+		await page.getByTestId('enable-audio').setChecked(options.enableAudio);
+	}

134-171: Two Peers tests duplicate setup that connectPeer already handles.

All 6 tests in this section manually navigate, fill room IDs, and click connect. The connectPeer helper defined at line 30 could reduce this boilerplate substantially. For tests needing sequential connection, you can still call connectPeer one at a time with intermediate assertions:

await connectPeer(page1, roomId);
await expect(page1.getByTestId('connection-state')).toContainText('signaling', { timeout: 5000 });
await connectPeer(page2, roomId);

793-797: Weak negative assertion — test can't distinguish between "error shown" and "stuck in signaling".

not.toContainText('connected') passes for any non-connected state (signaling, idle, error). If the goal is to verify an error or failure state is displayed, assert the expected state positively. This also avoids the full 10-second timeout wait that not.toContainText uses on the happy path.

Suggested improvement
-			// Should show error or transition to a failed state
-			// The reconnect should be disabled, so it should fail
-			await expect(page.getByTestId('connection-state')).not.toContainText('connected', {
-				timeout: 10000,
-			});
+			// With auto-reconnect disabled and an invalid signal URL, should reach idle or error state
+			await expect(page.getByTestId('connection-state')).toContainText(/idle|error/, {
+				timeout: 10000,
+			});
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33eb8b0 and d5e9278.

📒 Files selected for processing (2)
  • .github/workflows/package-smoke-test.yaml
  • e2e/webrtc.pw.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/package-smoke-test.yaml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Prettier formatter with tabs (width 3), single quotes, and semicolons for TypeScript files
Use TypeScript strict mode with ESNext target and bundler moduleResolution
Use StructuredError from @agentuity/core for error handling

Files:

  • e2e/webrtc.pw.ts
🧠 Learnings (1)
📚 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:

  • e2e/webrtc.pw.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Framework Integration Tests (TanStack & Next.js)
  • GitHub Check: Sandbox CLI Tests
  • GitHub Check: SDK Integration Test Suite
  • GitHub Check: Template Integration Tests
  • GitHub Check: Playwright E2E Smoke Test
  • GitHub Check: Package Installation & Usage Test
  • GitHub Check: Cloud Deployment Tests
  • GitHub Check: Build
  • GitHub Check: Pack & Upload
🔇 Additional comments (6)
e2e/webrtc.pw.ts (6)

7-28: Good mock pattern for getDisplayMedia in headless environments.

The fallback to getUserMedia when getDisplayMedia is unavailable or throws is a clean approach for testing screen sharing in headless Chromium.


59-115: Single Peer tests look solid.

Good coverage of initial state, signaling transition, clean disconnect, and custom room join.


555-777: Recording, Reconnection, Cursor Tracking, and Media Controls sections are well-structured.

Good coverage of recording lifecycle, reconnection with max-retry boundary, cursor canvas visibility, and mute toggle persistence. The try/finally cleanup pattern is consistently applied.


804-864: Good use of evaluate() to verify srcObject binding.

Checking that the video element's srcObject is non-null via evaluate() is the right approach since Playwright doesn't expose srcObject through its DOM API directly.


955-1085: Thorough state cleanup coverage.

Six tests systematically verify that connection state, peer ID, remote peer IDs, data channel, screen share state, and reconnect status all reset properly on disconnect. Good defensive testing.


1244-1279: Timestamp validation is well-designed.

Capturing beforeSend/afterSend boundaries around the action and using a 1-second tolerance for clock skew is a solid pattern for timing assertions in E2E tests.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

jhaynie and others added 2 commits February 5, 2026 21:28
- Use regex for JSON assertion to tolerate whitespace variations
- Fix comments to correctly reference peers[2] as third peer (not 'Peer 2')
- Handle enableVideo/enableAudio false case explicitly in connectPeer helper
- Replace weak negative assertion with positive regex match for error state
@jhaynie jhaynie merged commit 3522ea2 into main Feb 6, 2026
15 checks passed
@jhaynie jhaynie deleted the webrtc branch February 6, 2026 06:52
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