yaver-feedback-react-native 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # @yaver/feedback-react-native
1
+ # yaver-feedback-react-native
2
2
 
3
3
  Visual feedback SDK for Yaver. Lets testers and developers shake their phone (or tap a floating button) to capture screenshots, record voice notes, and send bug reports directly to a Yaver agent running on a dev machine. Built for vibe coding workflows where feedback needs to flow fast.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @yaver/feedback-react-native
8
+ npm install yaver-feedback-react-native
9
9
  ```
10
10
 
11
11
  ### Peer dependencies
@@ -26,7 +26,7 @@ npm install react-native-audio-recorder-player
26
26
  ## Quick Start
27
27
 
28
28
  ```tsx
29
- import { YaverFeedback, BlackBox, FeedbackModal } from '@yaver/feedback-react-native';
29
+ import { YaverFeedback, BlackBox, FeedbackModal } from 'yaver-feedback-react-native';
30
30
 
31
31
  // Initialize once at app startup
32
32
  YaverFeedback.init({
@@ -164,7 +164,7 @@ yaver sdk-token create --expires 24h
164
164
  Continuous streaming of app events to the agent. The agent keeps a ring buffer (last 1000 events per device) and injects context into fix prompts — so the AI agent already knows what the app was doing when you ask for a fix.
165
165
 
166
166
  ```tsx
167
- import { BlackBox } from '@yaver/feedback-react-native';
167
+ import { BlackBox } from 'yaver-feedback-react-native';
168
168
 
169
169
  // Start streaming (call after YaverFeedback.init)
170
170
  BlackBox.start({
@@ -219,6 +219,102 @@ BlackBox.isStreaming; // check if active
219
219
  | `state` | State mutations | `BlackBox.stateChange('theme toggled')` |
220
220
  | `render` | Component render with duration | `BlackBox.render('FlatList', 32.1)` |
221
221
 
222
+ ## Remote Reload (Agent Command Channel)
223
+
224
+ The SDK maintains a persistent SSE connection to the agent that receives commands. The primary use case: a vibe coder is away from their desk, coding on their phone via the Yaver mobile app, and wants to hot-reload the third-party app (with this SDK) running on the same or a different device.
225
+
226
+ ```
227
+ Yaver Mobile App Agent Third-Party App (SDK)
228
+ tap "Reload"
229
+ ───POST /dev/reload-app──► process reload
230
+ ├─ dev server reload
231
+ └─ BroadcastCommand("reload")
232
+ ───SSE push──────────────► BlackBox receives command
233
+ ├─ onReload() callback
234
+ └─ DevSettings.reload()
235
+ ```
236
+
237
+ Works over both direct LAN and relay connections since it's standard HTTP/SSE.
238
+
239
+ ### Setup
240
+
241
+ ```tsx
242
+ import { YaverFeedback, BlackBox } from 'yaver-feedback-react-native';
243
+
244
+ YaverFeedback.init({
245
+ authToken: 'your-sdk-token',
246
+ agentUrl: 'http://192.168.1.10:18080',
247
+
248
+ // Called when the vibe coder triggers reload from Yaver mobile app
249
+ onReload: () => {
250
+ console.log('Remote reload triggered!');
251
+ // Default behavior if omitted: DevSettings.reload() in dev mode
252
+ },
253
+
254
+ // Called when agent pushes a new native bundle
255
+ onReloadBundle: (bundleUrl, assetsUrl) => {
256
+ console.log('New bundle available at:', bundleUrl);
257
+ // Default: no-op. Implement custom bundle loading if needed.
258
+ },
259
+ });
260
+
261
+ // Start BlackBox — this also connects the command channel
262
+ BlackBox.start({ appName: 'AcmeStore' });
263
+ ```
264
+
265
+ ### Custom Command Handlers
266
+
267
+ For advanced use cases, register handlers directly on BlackBox:
268
+
269
+ ```tsx
270
+ // Listen for any command from the agent
271
+ const unsubscribe = BlackBox.onCommand((cmd) => {
272
+ switch (cmd.command) {
273
+ case 'reload':
274
+ // Hot reload from dev server
275
+ DevSettings.reload();
276
+ break;
277
+ case 'reload_bundle':
278
+ // Fetch new native bundle
279
+ const { bundleUrl, assetsUrl } = cmd.data;
280
+ loadNewBundle(bundleUrl, assetsUrl);
281
+ break;
282
+ default:
283
+ console.log('Unknown command:', cmd.command);
284
+ }
285
+ });
286
+
287
+ // Check connection status
288
+ BlackBox.isCommandChannelConnected; // boolean
289
+
290
+ // Clean up
291
+ unsubscribe();
292
+ ```
293
+
294
+ ### Triggering Reload from P2PClient
295
+
296
+ The SDK can also trigger reload programmatically (e.g., from a "Reload" button in the Feedback modal):
297
+
298
+ ```tsx
299
+ import { P2PClient } from 'yaver-feedback-react-native';
300
+
301
+ const client = new P2PClient('http://192.168.1.10:18080', 'your-token');
302
+
303
+ // Hot reload (dev server restart)
304
+ await client.reloadApp('dev');
305
+
306
+ // Rebuild native bundle + push to all connected SDK devices
307
+ await client.reloadApp('bundle');
308
+ ```
309
+
310
+ ### How It Works
311
+
312
+ 1. `BlackBox.start()` connects to `/blackbox/command-stream` via SSE
313
+ 2. The connection auto-reconnects on disconnect (5s backoff)
314
+ 3. When the agent receives `POST /dev/reload` or `POST /dev/reload-app`, it broadcasts a command to all connected SDK sessions
315
+ 4. The SDK invokes `onReload` / `onReloadBundle` callbacks, or falls back to `DevSettings.reload()` in dev mode
316
+ 5. Events are still sent via batch POST to `/blackbox/events` (unchanged)
317
+
222
318
  ### Resilience
223
319
 
224
320
  - Failed flushes re-add events to the buffer (capped at 2x maxBufferSize)
@@ -258,7 +354,7 @@ YaverFeedback.init({
258
354
  ### Manual discovery
259
355
 
260
356
  ```typescript
261
- import { YaverDiscovery } from '@yaver/feedback-react-native';
357
+ import { YaverDiscovery } from 'yaver-feedback-react-native';
262
358
 
263
359
  // Full discovery (Convex → stored → LAN scan)
264
360
  const result = await YaverDiscovery.discover({
@@ -281,7 +377,7 @@ await YaverDiscovery.clear();
281
377
  A full-screen UI for discovering and connecting to a Yaver agent. Shows connection status, URL/token inputs, auto-discover button, and a Start/Stop testing toggle with recording timer.
282
378
 
283
379
  ```tsx
284
- import { YaverConnectionScreen } from '@yaver/feedback-react-native';
380
+ import { YaverConnectionScreen } from 'yaver-feedback-react-native';
285
381
 
286
382
  function App() {
287
383
  return (
@@ -445,6 +541,10 @@ YaverFeedback.init({
445
541
  feedbackMode: 'batch', // 'live' | 'narrated' | 'batch' (default: 'batch')
446
542
  agentCommentaryLevel: 0, // 0-10 (default: 0, only relevant in live mode)
447
543
  maxCapturedErrors: 5, // Error ring buffer size (default: 5)
544
+
545
+ // Remote reload callbacks (from Yaver mobile app or another SDK device)
546
+ onReload: () => { ... }, // Called on hot reload command (default: DevSettings.reload())
547
+ onReloadBundle: (url, assets) => { ... },// Called on native bundle rebuild command
448
548
  });
449
549
  ```
450
550
 
@@ -463,7 +563,7 @@ YaverFeedback.init({ authToken, trigger: 'shake' });
463
563
  A small draggable "Y" button overlays the app. Tap to open the feedback modal.
464
564
 
465
565
  ```tsx
466
- import { FloatingButton, FeedbackModal, YaverFeedback } from '@yaver/feedback-react-native';
566
+ import { FloatingButton, FeedbackModal, YaverFeedback } from 'yaver-feedback-react-native';
467
567
 
468
568
  function App() {
469
569
  return (
@@ -481,7 +581,7 @@ function App() {
481
581
  Trigger feedback collection programmatically from anywhere in your app.
482
582
 
483
583
  ```typescript
484
- import { YaverFeedback } from '@yaver/feedback-react-native';
584
+ import { YaverFeedback } from 'yaver-feedback-react-native';
485
585
 
486
586
  // In a button handler, debug menu, etc.
487
587
  YaverFeedback.startReport();
@@ -492,7 +592,7 @@ YaverFeedback.startReport();
492
592
  For direct communication with the Yaver agent beyond feedback:
493
593
 
494
594
  ```typescript
495
- import { P2PClient } from '@yaver/feedback-react-native';
595
+ import { P2PClient } from 'yaver-feedback-react-native';
496
596
 
497
597
  const client = new P2PClient('http://192.168.1.10:18080', 'your-token');
498
598
 
@@ -608,6 +708,8 @@ YaverFeedback.setEnabled(false);
608
708
  | `wrapConsole()` | Intercept console.log/warn/error |
609
709
  | `unwrapConsole()` | Restore original console methods |
610
710
  | `wrapErrorHandler(next?)` | Pass-through error handler with real-time streaming |
711
+ | `onCommand(handler)` | Register handler for agent commands (reload, etc.). Returns unsubscribe fn |
712
+ | `isCommandChannelConnected` | Whether the SSE command channel is connected (getter) |
611
713
 
612
714
  ### P2PClient
613
715
 
@@ -622,6 +724,7 @@ YaverFeedback.setEnabled(false);
622
724
  | `getArtifactUrl(buildId)` | Get download URL for a build artifact |
623
725
  | `voiceStatus()` | Get voice capability info |
624
726
  | `transcribeVoice(audioUri)` | Send audio for transcription |
727
+ | `reloadApp(mode)` | Trigger remote reload: `'dev'` (hot reload) or `'bundle'` (rebuild + push) |
625
728
  | `startTestSession()` | Start autonomous test session |
626
729
  | `stopTestSession()` | Stop test session |
627
730
  | `getTestSession()` | Get test session status + fixes |
@@ -655,8 +758,10 @@ The SDK communicates with these agent HTTP endpoints:
655
758
  | `/feedback` | POST | Upload feedback bundle (multipart) |
656
759
  | `/feedback/stream` | POST | Stream live feedback events |
657
760
  | `/blackbox/events` | POST | Batch stream Black Box events |
761
+ | `/blackbox/command-stream` | GET | SSE command channel (agent pushes reload, etc.) |
658
762
  | `/blackbox/subscribe` | GET | SSE live log stream |
659
763
  | `/blackbox/context` | GET | Get generated prompt context |
764
+ | `/dev/reload-app` | POST | Trigger remote reload of SDK-connected apps |
660
765
  | `/builds` | GET/POST | List or start builds |
661
766
  | `/voice/status` | GET | Voice capability info |
662
767
  | `/voice/transcribe` | POST | Send audio for transcription |
@@ -667,23 +772,24 @@ The SDK communicates with these agent HTTP endpoints:
667
772
  ## Architecture
668
773
 
669
774
  ```
670
- ┌──────────────────┐ HTTP (Bearer auth) ┌──────────────────┐
671
- │ Your App │────────────────────────────►│ Yaver Agent │
672
- │ + Feedback SDK │ feedback, blackbox events │ (Go CLI) │
673
- │ + BlackBox │ screenshots, voice, video │ on your machine │
674
- │ │◄────────────────────────────│ │
675
- │ │ fixes, build status, voice │ runs AI agent │
676
- └──────────────────┘ └──────────────────┘
677
- │ │
678
- Auth only Auth only
679
- ▼ ▼
680
- ┌─────────────────────────────────────────────────────────────────────┐
681
- │ Convex Backend │
682
- Token validation + device registry (no task data stored)
683
- └─────────────────────────────────────────────────────────────────────┘
684
- ```
685
-
686
- All feedback data flows P2P between your app and the agent. Convex handles only auth and device discovery.
775
+ ┌──────────────────┐ HTTP (Bearer auth) ┌──────────────────┐ HTTP/SSE ┌──────────────────┐
776
+ │ Your App │────────────────────────────►│ Yaver Agent │◄────────────────── Yaver Mobile │
777
+ │ + Feedback SDK │ feedback, blackbox events │ (Go CLI) │ tasks, reload │ App (phone) │
778
+ │ + BlackBox │ screenshots, voice, video │ on your machine │ commands │ vibe coding │
779
+ │ │◄────────────────────────────│ │ │ │
780
+ │ │ reload commands (SSE), │ runs AI agent │ │ │
781
+ │ │ fixes, build status, voice │ │ │ │
782
+ └──────────────────┘ └──────────────────┘ └──────────────────┘
783
+
784
+ │ Auth only │ Auth only │
785
+ ▼ ▼ ▼
786
+ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────┐
787
+ Convex Backend
788
+ │ Token validation + device registry (no task data stored) │
789
+ └──────────────────────────────────────────────────────────────────────────────────────────────────────────┘
790
+ ```
791
+
792
+ All feedback data flows P2P between your app and the agent. The Yaver mobile app can trigger remote reloads that reach your app via the agent's command channel. Convex handles only auth and device discovery.
687
793
 
688
794
  ## License
689
795
 
package/app.plugin.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Expo config plugin for @yaver/feedback-react-native.
2
+ * Expo config plugin for yaver-feedback-react-native.
3
3
  *
4
4
  * Adds required native permissions for the feedback SDK:
5
5
  * - iOS: Camera + Microphone usage descriptions
6
6
  * - Android: CAMERA + RECORD_AUDIO permissions
7
7
  *
8
8
  * Usage in app.json:
9
- * { "expo": { "plugins": ["@yaver/feedback-react-native"] } }
9
+ * { "expo": { "plugins": ["yaver-feedback-react-native"] } }
10
10
  */
11
11
  const {
12
12
  withInfoPlist,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Visual feedback SDK for Yaver — shake-to-report, screen recording, voice annotations for vibe coding",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/BlackBox.ts CHANGED
@@ -49,6 +49,15 @@ export interface BlackBoxConfig {
49
49
  * - Use `BlackBox.wrapConsole()` to intercept console.log/warn/error
50
50
  * (only if you explicitly opt in — no auto-hooking)
51
51
  */
52
+ /** Command received from the agent via the SSE command channel. */
53
+ export interface BlackBoxCommand {
54
+ command: string; // "reload", "reload_bundle"
55
+ data?: Record<string, unknown>; // e.g. { bundleUrl: "/dev/native-bundle" }
56
+ }
57
+
58
+ /** Callback type for handling agent commands. */
59
+ export type CommandHandler = (cmd: BlackBoxCommand) => void;
60
+
52
61
  export class BlackBox {
53
62
  private static baseUrl: string | null = null;
54
63
  private static authToken: string | null = null;
@@ -65,6 +74,12 @@ export class BlackBox {
65
74
  error: typeof console.error;
66
75
  } | null = null;
67
76
 
77
+ // SSE command channel — persistent connection to /blackbox/stream
78
+ private static sseAbortController: AbortController | null = null;
79
+ private static sseReconnectTimer: ReturnType<typeof setTimeout> | null = null;
80
+ private static commandHandlers: CommandHandler[] = [];
81
+ private static sseConnected = false;
82
+
68
83
  /**
69
84
  * Start the black box stream. Call after `YaverFeedback.init()`.
70
85
  *
@@ -97,6 +112,9 @@ export class BlackBox {
97
112
  message: 'Black box streaming started',
98
113
  timestamp: Date.now(),
99
114
  });
115
+
116
+ // Connect SSE command channel for receiving agent commands (reload, etc.)
117
+ BlackBox.connectSSE();
100
118
  }
101
119
 
102
120
  /** Stop the black box stream and flush remaining events. */
@@ -112,6 +130,7 @@ export class BlackBox {
112
130
  clearInterval(BlackBox.flushTimer);
113
131
  BlackBox.flushTimer = null;
114
132
  }
133
+ BlackBox.disconnectSSE();
115
134
  BlackBox.started = false;
116
135
  }
117
136
 
@@ -273,6 +292,137 @@ export class BlackBox {
273
292
  };
274
293
  }
275
294
 
295
+ // ─── Command channel (agent → SDK) ──────────────────────────────
296
+
297
+ /**
298
+ * Register a handler for commands pushed by the agent.
299
+ * The primary use case is receiving "reload" commands when the vibe coder
300
+ * triggers a reload from the Yaver mobile app.
301
+ *
302
+ * @example
303
+ * BlackBox.onCommand((cmd) => {
304
+ * if (cmd.command === 'reload') {
305
+ * DevSettings.reload(); // or Updates.reloadAsync()
306
+ * }
307
+ * });
308
+ */
309
+ static onCommand(handler: CommandHandler): () => void {
310
+ BlackBox.commandHandlers.push(handler);
311
+ // Return unsubscribe function
312
+ return () => {
313
+ BlackBox.commandHandlers = BlackBox.commandHandlers.filter(h => h !== handler);
314
+ };
315
+ }
316
+
317
+ /** Whether the SSE command channel is connected. */
318
+ static get isCommandChannelConnected(): boolean {
319
+ return BlackBox.sseConnected;
320
+ }
321
+
322
+ /**
323
+ * Connect to the agent's /blackbox/stream SSE endpoint.
324
+ * This persistent connection allows the agent to push commands (reload, etc.)
325
+ * back to the SDK. Events are still sent via batch POST /blackbox/events.
326
+ */
327
+ private static async connectSSE(): Promise<void> {
328
+ if (!BlackBox.baseUrl || !BlackBox.authToken) return;
329
+
330
+ // Disconnect any existing connection
331
+ BlackBox.disconnectSSE();
332
+
333
+ const controller = new AbortController();
334
+ BlackBox.sseAbortController = controller;
335
+
336
+ const url = `${BlackBox.baseUrl}/blackbox/command-stream?device=${encodeURIComponent(BlackBox.deviceId)}`;
337
+
338
+ try {
339
+ const response = await fetch(url, {
340
+ method: 'GET',
341
+ headers: {
342
+ Authorization: `Bearer ${BlackBox.authToken}`,
343
+ 'X-Device-ID': BlackBox.deviceId,
344
+ 'X-Platform': Platform.OS,
345
+ 'X-App-Name': BlackBox.appName,
346
+ Accept: 'text/event-stream',
347
+ },
348
+ // @ts-ignore — React Native supports signal on fetch
349
+ signal: controller.signal,
350
+ });
351
+
352
+ if (!response.ok || !response.body) {
353
+ BlackBox.scheduleSSEReconnect();
354
+ return;
355
+ }
356
+
357
+ BlackBox.sseConnected = true;
358
+
359
+ // Read SSE stream
360
+ const reader = response.body.getReader();
361
+ const decoder = new TextDecoder();
362
+ let buffer = '';
363
+
364
+ while (true) {
365
+ const { done, value } = await reader.read();
366
+ if (done) break;
367
+
368
+ buffer += decoder.decode(value, { stream: true });
369
+ const lines = buffer.split('\n');
370
+ buffer = lines.pop() ?? '';
371
+
372
+ for (const line of lines) {
373
+ if (!line.startsWith('data: ')) continue;
374
+ try {
375
+ const msg = JSON.parse(line.slice(6));
376
+ if (msg.type === 'command' && msg.command) {
377
+ const cmd: BlackBoxCommand = msg.command;
378
+ for (const handler of BlackBox.commandHandlers) {
379
+ try {
380
+ handler(cmd);
381
+ } catch {
382
+ // Handler error — don't break the loop
383
+ }
384
+ }
385
+ }
386
+ } catch {
387
+ // Malformed SSE data — skip
388
+ }
389
+ }
390
+ }
391
+ } catch (err: unknown) {
392
+ // AbortError is expected on disconnect
393
+ if (err instanceof Error && err.name === 'AbortError') return;
394
+ } finally {
395
+ BlackBox.sseConnected = false;
396
+ }
397
+
398
+ // Reconnect if still running
399
+ if (BlackBox.started) {
400
+ BlackBox.scheduleSSEReconnect();
401
+ }
402
+ }
403
+
404
+ private static disconnectSSE(): void {
405
+ if (BlackBox.sseAbortController) {
406
+ BlackBox.sseAbortController.abort();
407
+ BlackBox.sseAbortController = null;
408
+ }
409
+ if (BlackBox.sseReconnectTimer) {
410
+ clearTimeout(BlackBox.sseReconnectTimer);
411
+ BlackBox.sseReconnectTimer = null;
412
+ }
413
+ BlackBox.sseConnected = false;
414
+ }
415
+
416
+ private static scheduleSSEReconnect(): void {
417
+ if (!BlackBox.started) return;
418
+ if (BlackBox.sseReconnectTimer) return;
419
+ // Reconnect after 5s
420
+ BlackBox.sseReconnectTimer = setTimeout(() => {
421
+ BlackBox.sseReconnectTimer = null;
422
+ if (BlackBox.started) BlackBox.connectSSE();
423
+ }, 5000);
424
+ }
425
+
276
426
  // ─── Internal ────────────────────────────────────────────────────
277
427
 
278
428
  private static push(event: BlackBoxEvent): void {
@@ -224,13 +224,13 @@ export const FeedbackModal: React.FC = () => {
224
224
 
225
225
  setIsReloading(true);
226
226
  try {
227
- const response = await fetch(`${config.agentUrl.replace(/\/$/, '')}/exec`, {
227
+ const response = await fetch(`${config.agentUrl.replace(/\/$/, '')}/dev/reload-app`, {
228
228
  method: 'POST',
229
229
  headers: {
230
230
  Authorization: `Bearer ${config.authToken}`,
231
231
  'Content-Type': 'application/json',
232
232
  },
233
- body: JSON.stringify({ command: 'reload', type: 'hot-reload' }),
233
+ body: JSON.stringify({ mode: 'dev' }),
234
234
  });
235
235
  if (response.ok) {
236
236
  BlackBox.lifecycle('Hot reload triggered from feedback SDK');
package/src/P2PClient.ts CHANGED
@@ -214,6 +214,32 @@ export class P2PClient {
214
214
  };
215
215
  }
216
216
 
217
+ /**
218
+ * Trigger a reload of the third-party app.
219
+ * In dev mode, this tells the dev server to hot-reload.
220
+ * In bundle mode, this rebuilds the native bundle and pushes it.
221
+ * The reload command is also broadcast to all connected SDK devices
222
+ * via the BlackBox command channel.
223
+ * @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
224
+ */
225
+ async reloadApp(mode: 'dev' | 'bundle' = 'dev'): Promise<{ ok: boolean }> {
226
+ const response = await fetch(`${this.baseUrl}/dev/reload-app`, {
227
+ method: 'POST',
228
+ headers: {
229
+ Authorization: `Bearer ${this.authToken}`,
230
+ 'Content-Type': 'application/json',
231
+ },
232
+ body: JSON.stringify({ mode }),
233
+ });
234
+
235
+ if (!response.ok) {
236
+ const text = await response.text().catch(() => '');
237
+ throw new Error(`[P2PClient] Reload app failed (${response.status}): ${text}`);
238
+ }
239
+
240
+ return response.json();
241
+ }
242
+
217
243
  /** Get the download URL for a build artifact. */
218
244
  getArtifactUrl(buildId: string): string {
219
245
  return `${this.baseUrl}/builds/${buildId}/artifact`;
@@ -71,6 +71,35 @@ export class YaverFeedback {
71
71
  });
72
72
  }
73
73
 
74
+ // Wire up BlackBox command handlers for reload signals from the agent.
75
+ // This enables the vibe coder to trigger reload from the Yaver mobile app
76
+ // and have the third-party app (with this SDK) automatically reload.
77
+ if (enabled) {
78
+ BlackBox.onCommand((cmd) => {
79
+ if (cmd.command === 'reload') {
80
+ if (cfg.onReload) {
81
+ cfg.onReload();
82
+ } else {
83
+ // Default: try DevSettings.reload() in dev mode
84
+ try {
85
+ const { DevSettings } = require('react-native');
86
+ if (typeof DevSettings?.reload === 'function') {
87
+ DevSettings.reload();
88
+ }
89
+ } catch {
90
+ // Not in dev mode or DevSettings unavailable
91
+ }
92
+ }
93
+ } else if (cmd.command === 'reload_bundle' && cmd.data) {
94
+ const bundleUrl = cmd.data.bundleUrl as string;
95
+ const assetsUrl = cmd.data.assetsUrl as string | undefined;
96
+ if (cfg.onReloadBundle) {
97
+ cfg.onReloadBundle(bundleUrl, assetsUrl);
98
+ }
99
+ }
100
+ });
101
+ }
102
+
74
103
  // NOTE: We intentionally do NOT hook ErrorUtils.setGlobalHandler().
75
104
  // Sentry, Crashlytics, Bugsnag, and other tools all compete for that
76
105
  // single slot. Hijacking it would break whichever tool the developer
package/src/index.ts CHANGED
@@ -49,6 +49,6 @@ export type {
49
49
  TestFix,
50
50
  TestSession,
51
51
  } from './types';
52
- export type { BlackBoxEvent, BlackBoxConfig } from './BlackBox';
52
+ export type { BlackBoxEvent, BlackBoxConfig, BlackBoxCommand, CommandHandler } from './BlackBox';
53
53
  export type { DiscoveryResult } from './Discovery';
54
54
  export type { FeedbackEvent } from './P2PClient';
package/src/types.ts CHANGED
@@ -102,6 +102,30 @@ export interface FeedbackConfig {
102
102
  * panelBackgroundColor: '#333333' // neutral dark gray
103
103
  */
104
104
  panelBackgroundColor?: string;
105
+ /**
106
+ * Callback invoked when the agent pushes a reload command.
107
+ * This happens when the vibe coder triggers a reload from the Yaver mobile app
108
+ * or from another connected device.
109
+ *
110
+ * If not provided, the SDK will attempt `DevSettings.reload()` in dev mode
111
+ * or ignore the command in production.
112
+ *
113
+ * @example
114
+ * onReload: () => {
115
+ * // Custom reload logic, e.g. re-fetch bundle from agent
116
+ * Updates.reloadAsync();
117
+ * }
118
+ */
119
+ onReload?: () => void;
120
+ /**
121
+ * Callback invoked when the agent pushes a reload_bundle command with a new
122
+ * native bundle URL. The SDK passes the bundle URL and assets URL so the app
123
+ * can fetch and load the new bundle.
124
+ *
125
+ * If not provided, the SDK will attempt to POST the bundle to localhost:8347
126
+ * (Yaver's on-device HTTP server) for native container reload.
127
+ */
128
+ onReloadBundle?: (bundleUrl: string, assetsUrl?: string) => void;
105
129
  /**
106
130
  * TLS certificate fingerprint (SHA256) for HTTPS on LAN.
107
131
  * When set, the SDK prefers HTTPS connections and verifies the agent's