yaver-feedback-react-native 0.8.8 → 0.8.10

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.
@@ -1049,7 +1049,15 @@ const RunnerAuthNativeModal = ({ runner, onClose }) => {
1049
1049
  const [session, setSession] = (0, react_1.useState)(null);
1050
1050
  const [startError, setStartError] = (0, react_1.useState)(null);
1051
1051
  const [copied, setCopied] = (0, react_1.useState)(false);
1052
+ const [pasteCode, setPasteCode] = (0, react_1.useState)('');
1053
+ const [submitting, setSubmitting] = (0, react_1.useState)(false);
1054
+ const [submitError, setSubmitError] = (0, react_1.useState)(null);
1052
1055
  const startedRef = (0, react_1.useRef)(false);
1056
+ // Claude is the only runner that needs the user to paste a verifier
1057
+ // code back from platform.claude.com's callback page; Codex device-
1058
+ // auth and OpenCode (no OAuth at all) bypass this. Mirrors the
1059
+ // requiresPasteBack check in iOS YaverRunnerAuthFlowPane.swift.
1060
+ const needsPasteBack = runner === 'claude' || runner === 'claude-code';
1053
1061
  (0, react_1.useEffect)(() => {
1054
1062
  if (startedRef.current)
1055
1063
  return;
@@ -1164,9 +1172,68 @@ const RunnerAuthNativeModal = ({ runner, onClose }) => {
1164
1172
  </react_native_1.Text>
1165
1173
  </react_native_1.Pressable>
1166
1174
  </react_native_1.View>) : null}
1175
+ {needsPasteBack ? (<react_native_1.View style={{ marginTop: 14 }}>
1176
+ <react_native_1.Text style={runnerAuthModalStyles.codeLabel}>
1177
+ PASTE CODE FROM CLAUDE.COM
1178
+ </react_native_1.Text>
1179
+ <react_native_1.View style={{ flexDirection: 'row', gap: 8, marginTop: 6 }}>
1180
+ <react_native_1.View style={{
1181
+ flex: 1,
1182
+ backgroundColor: 'rgba(148,163,184,0.10)',
1183
+ borderRadius: 10,
1184
+ paddingHorizontal: 10,
1185
+ }}>
1186
+ {/* Lazy-import TextInput so the SDK doesn't pull
1187
+ extra surface from react-native at module load. */}
1188
+ {(() => {
1189
+ const { TextInput } = require('react-native');
1190
+ return (<TextInput value={pasteCode} onChangeText={(t) => {
1191
+ setPasteCode(t);
1192
+ setSubmitError(null);
1193
+ }} placeholder="paste code here" placeholderTextColor="#64748b" autoCapitalize="none" autoCorrect={false} spellCheck={false} style={{ color: '#f1f5f9', fontSize: 14, paddingVertical: 10 }}/>);
1194
+ })()}
1195
+ </react_native_1.View>
1196
+ <react_native_1.Pressable disabled={!pasteCode.trim() || submitting} onPress={async () => {
1197
+ if (!session || !pasteCode.trim())
1198
+ return;
1199
+ setSubmitting(true);
1200
+ setSubmitError(null);
1201
+ try {
1202
+ const next = await YaverFeedback_1.YaverFeedback.submitRunnerBrowserAuthCode(session.id, pasteCode.trim());
1203
+ setSession(next);
1204
+ setPasteCode('');
1205
+ }
1206
+ catch (err) {
1207
+ setSubmitError(err instanceof Error ? err.message : String(err));
1208
+ }
1209
+ finally {
1210
+ setSubmitting(false);
1211
+ }
1212
+ }} style={{
1213
+ paddingHorizontal: 14,
1214
+ justifyContent: 'center',
1215
+ backgroundColor: !pasteCode.trim() || submitting
1216
+ ? 'rgba(124,58,237,0.4)'
1217
+ : '#7c3aed',
1218
+ borderRadius: 10,
1219
+ }}>
1220
+ <react_native_1.Text style={{ color: 'white', fontWeight: '600' }}>
1221
+ {submitting ? '…' : 'Submit'}
1222
+ </react_native_1.Text>
1223
+ </react_native_1.Pressable>
1224
+ </react_native_1.View>
1225
+ {submitError ? (<react_native_1.Text style={{
1226
+ marginTop: 6,
1227
+ color: '#fca5a5',
1228
+ fontSize: 12,
1229
+ }}>
1230
+ {submitError}
1231
+ </react_native_1.Text>) : null}
1232
+ </react_native_1.View>) : null}
1167
1233
  <react_native_1.Text style={runnerAuthModalStyles.phishingHint}>
1168
- Device codes are a common phishing target. Never share this code. This dialog
1169
- turns green automatically once sign-in completes.
1234
+ {needsPasteBack
1235
+ ? 'After authorising on platform.claude.com, copy the code from the callback page and paste it above. Never share this code.'
1236
+ : 'Device codes are a common phishing target. Never share this code. This dialog turns green automatically once sign-in completes.'}
1170
1237
  </react_native_1.Text>
1171
1238
  </react_native_1.View>)}
1172
1239
  </react_native_1.View>
@@ -47,6 +47,12 @@ export declare class P2PClient {
47
47
  startRunnerBrowserAuth(runner: string): Promise<RunnerBrowserAuthSession>;
48
48
  getRunnerBrowserAuthStatus(sessionId: string): Promise<RunnerBrowserAuthSession>;
49
49
  cancelRunnerBrowserAuth(sessionId: string): Promise<void>;
50
+ /** Submit the verifier code Anthropic shows on the callback page so
51
+ * the agent can finalise claude CLI's OAuth handshake. Codex doesn't
52
+ * use this — its device-auth flow auto-resolves via polling — but
53
+ * the SDK still exposes it for symmetry with mobile/src/components/
54
+ * RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
55
+ submitRunnerBrowserAuthCode(sessionId: string, code: string): Promise<RunnerBrowserAuthSession>;
50
56
  capabilitySnapshot(): Promise<CapabilitySnapshot | null>;
51
57
  incidents(opts?: {
52
58
  category?: string;
package/dist/P2PClient.js CHANGED
@@ -155,6 +155,25 @@ class P2PClient {
155
155
  }
156
156
  catch { /* best-effort */ }
157
157
  }
158
+ /** Submit the verifier code Anthropic shows on the callback page so
159
+ * the agent can finalise claude CLI's OAuth handshake. Codex doesn't
160
+ * use this — its device-auth flow auto-resolves via polling — but
161
+ * the SDK still exposes it for symmetry with mobile/src/components/
162
+ * RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
163
+ async submitRunnerBrowserAuthCode(sessionId, code) {
164
+ const url = `${this.baseUrl}/runner-auth/browser/submit-code`;
165
+ const resp = await fetch(url, {
166
+ method: 'POST',
167
+ headers: { ...this.authHeaders(), 'Content-Type': 'application/json' },
168
+ body: JSON.stringify({ id: sessionId, code }),
169
+ });
170
+ if (!resp.ok) {
171
+ const text = await resp.text().catch(() => '');
172
+ throw new Error(`submitRunnerBrowserAuthCode HTTP ${resp.status}: ${text}`);
173
+ }
174
+ const data = await resp.json();
175
+ return data.session;
176
+ }
158
177
  async capabilitySnapshot() {
159
178
  try {
160
179
  const resp = await fetch(`${this.baseUrl}/capabilities/snapshot`, { headers: this.authHeaders() });
@@ -78,6 +78,10 @@ export declare class YaverFeedback {
78
78
  static startRunnerBrowserAuth(runner: string): Promise<import('./types').RunnerBrowserAuthSession>;
79
79
  static getRunnerBrowserAuthStatus(sessionId: string): Promise<import('./types').RunnerBrowserAuthSession>;
80
80
  static cancelRunnerBrowserAuth(sessionId: string): Promise<void>;
81
+ /** Submit the Claude paste-back verifier so the agent can finalise the
82
+ * OAuth handshake. RunnerAuthModal calls this after the user copies
83
+ * the code from platform.claude.com's callback page. */
84
+ static submitRunnerBrowserAuthCode(sessionId: string, code: string): Promise<import('./types').RunnerBrowserAuthSession>;
81
85
  /**
82
86
  * Sign out: clear cached token + device, tear down the P2P client. The
83
87
  * SDK stays enabled; the next feedback trigger will re-prompt for login.
@@ -13,10 +13,13 @@ const preferences_1 = require("./preferences");
13
13
  // (mobile/ios/Yaver/YaverInfo.{swift,m} + Android counterpart); a
14
14
  // standalone app bundled by its own developer has no such module.
15
15
  // When the SDK is loaded through Yaver's Hermes-push guest runtime we
16
- // deliberately no-op every public entry point Yaver owns the shake
17
- // gesture ("Reload / Back to Yaver" overlay), the feedback capture
18
- // flow, and the BlackBox streaming; running a second copy from inside
19
- // the guest just produces duplicate UIs and double P2P sessions.
16
+ // run in HOST MODE: dormant by default (no shake detector, no auto
17
+ // BlackBox, no QuickActionIcon Yaver's host shell owns those), but
18
+ // we DO register a DeviceEventEmitter listener so Yaver's overlay can
19
+ // flip the SDK live at runtime. When the user shakes inside the guest
20
+ // app and taps "Feedback" on the Yaver overlay, AppDelegate dispatches
21
+ // `yaverFeedback:startReport` into this bridge; the listener wakes the
22
+ // SDK and opens the modal in-place over the running guest UI.
20
23
  function isRunningInsideYaverHost() {
21
24
  try {
22
25
  return !!react_native_1.NativeModules?.YaverInfo;
@@ -25,11 +28,108 @@ function isRunningInsideYaverHost() {
25
28
  return false;
26
29
  }
27
30
  }
28
- // Suppresses SDK activation when inside Yaver super-host. Callers of
29
- // YaverFeedback.init / startReport / startBatchRecording / … early-out
30
- // by checking this first so the SDK's side effects (accelerometer,
31
- // BlackBox HTTP, SSE command channel, FeedbackModal mount) never start.
32
- const YAVER_HOST_SUPPRESS = isRunningInsideYaverHost();
31
+ // Two distinct compile-time modes for a guest app like sfmg / talos:
32
+ //
33
+ // YAVER_HOST_MODE — bundled by Yaver's agent (/dev/build-native) for
34
+ // loading inside Yaver mobile. SDK code is in the
35
+ // bundle but boots PASSIVE: no shake detector, no
36
+ // auto-BlackBox, no container UI. Yaver's host
37
+ // overlay owns the shake gesture; when the user
38
+ // taps "Feedback" on Yaver's overlay, AppDelegate
39
+ // dispatches yaverFeedback:hostActivate into this
40
+ // bridge and the SDK runtime-flips active for a
41
+ // single feedback session.
42
+ //
43
+ // YAVER_SDK_MODE — sfmg's own standalone TestFlight / Play Store
44
+ // build, with the Yaver SDK embedded. SDK boots
45
+ // active: shake → modal directly, no Yaver host
46
+ // involved. Default for normal `expo build`.
47
+ //
48
+ // Both can be forced at build time via process.env. When neither is
49
+ // set, fall back to runtime detection: if the YaverInfo native module
50
+ // exists (we're inside Yaver), assume HOST_MODE; else SDK_MODE. This
51
+ // keeps older bundles (built before the agent learned to set the env)
52
+ // working unchanged.
53
+ const YAVER_HOST_MODE_BUILD = (() => {
54
+ try {
55
+ const v = process.env?.YAVER_HOST_MODE;
56
+ return v === 'true' || v === '1' || v === true || v === 1;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ })();
62
+ const YAVER_SDK_MODE_BUILD = (() => {
63
+ try {
64
+ const v = process.env?.YAVER_SDK_MODE;
65
+ return v === 'true' || v === '1' || v === true || v === 1;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ })();
71
+ // Effective mode after considering build flags AND runtime detection.
72
+ const IS_HOST_MODE = YAVER_HOST_MODE_BUILD ||
73
+ (!YAVER_SDK_MODE_BUILD && isRunningInsideYaverHost());
74
+ // Tracks whether we've been runtime-activated by the host (sfmg-in-Yaver
75
+ // case). Independent of `enabled` so we can tell "host turned us on for
76
+ // one shot" apart from "developer toggled enabled programmatically".
77
+ let hostActivated = false;
78
+ // Host-activation listener. Always registered when in HOST mode so a
79
+ // Yaver overlay tap can wake the SDK even before the guest's
80
+ // YaverFeedback.init() runs. AppDelegate (mobile/ios/Yaver/AppDelegate.
81
+ // swift::handleFeedbackTap) sends `yaverFeedback:startReport` into the
82
+ // guest bridge when the user picks Feedback on the shake overlay.
83
+ //
84
+ // Activation flow:
85
+ // 1. Try Yaver's existing bearer (NativeModules.YaverInfo.
86
+ // inheritedAuthToken, populated by Yaver mobile's auth.ts on
87
+ // sign-in). Validate against /auth/validate before trusting.
88
+ // 2. If valid: setAuthToken + open feedback modal in-place (the
89
+ // modal already supports hot reload, screenshot, vibing chat).
90
+ // 3. If missing or invalid: open the SDK's own login screen.
91
+ if (IS_HOST_MODE) {
92
+ try {
93
+ const { DeviceEventEmitter } = require('react-native');
94
+ DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
95
+ hostActivated = true;
96
+ enabled = true;
97
+ void (async () => {
98
+ const yi = react_native_1.NativeModules?.YaverInfo;
99
+ const inheritedToken = String(yi?.inheritedAuthToken || '').trim();
100
+ const inheritedAgent = String(yi?.inheritedAgentUrl || '').trim();
101
+ const inheritedDevice = String(yi?.inheritedDeviceId || '').trim();
102
+ if (inheritedToken) {
103
+ // Lazy-import auth.ts so module load doesn't drag the auth
104
+ // network code into the active set when the SDK is dormant.
105
+ const { validateToken } = require('./auth');
106
+ const user = await validateToken(inheritedToken).catch(() => null);
107
+ if (user) {
108
+ // Seed config + connect the SDK to the host's auth.
109
+ if (!config) {
110
+ YaverFeedback.init({
111
+ authToken: inheritedToken,
112
+ agentUrl: inheritedAgent || undefined,
113
+ preferredDeviceId: inheritedDevice || undefined,
114
+ });
115
+ }
116
+ await YaverFeedback.setAuthToken(inheritedToken);
117
+ await YaverFeedback.startReport();
118
+ return;
119
+ }
120
+ }
121
+ // No token, or token invalid — fall through to the SDK's own
122
+ // login screen. The user picks an OAuth provider; on success
123
+ // the modal continues with the new token.
124
+ if (!config) {
125
+ YaverFeedback.init({});
126
+ }
127
+ YaverFeedback.showLogin();
128
+ })();
129
+ });
130
+ }
131
+ catch { /* react-native unavailable in jsdom unit tests */ }
132
+ }
33
133
  let config = null;
34
134
  let enabled = false;
35
135
  let p2pClient = null;
@@ -102,17 +202,34 @@ class YaverFeedback {
102
202
  * via `YaverDiscovery` on the first `startReport()` call.
103
203
  */
104
204
  static init(cfg) {
105
- if (YAVER_HOST_SUPPRESS) {
106
- // Running inside Yaver's super-host — yield to Yaver's native UX.
107
- enabled = false;
108
- return;
109
- }
110
205
  config = {
111
206
  trigger: 'shake',
112
207
  maxRecordingDuration: 120,
113
208
  autoLogin: true,
114
209
  ...cfg,
115
210
  };
211
+ if (IS_HOST_MODE) {
212
+ // sfmg / talos / etc. running inside Yaver mobile (compile-time
213
+ // YAVER_HOST_MODE or runtime-detected). Store config so a later
214
+ // host activation (yaverFeedback:startReport from AppDelegate)
215
+ // can open the modal — but skip the side effects Yaver's host
216
+ // shell owns: shake detector, auto-BlackBox, QuickActionIcon.
217
+ enabled = false;
218
+ // Configure auth endpoints + strict-native-auth even in passive
219
+ // mode so a host-activated session uses the same login routing
220
+ // the standalone path would.
221
+ (0, auth_1.configureAuthEndpoints)({
222
+ convexSiteUrl: cfg.authConvexSiteUrl,
223
+ webBaseUrl: cfg.authWebBaseUrl,
224
+ });
225
+ (0, auth_1.setStrictNativeAuth)(cfg.strictNativeAuth === true);
226
+ if (!config.convexUrl) {
227
+ config.convexUrl = cfg.authConvexSiteUrl ?? auth_1.DEFAULT_CONVEX_SITE_URL;
228
+ }
229
+ maxErrors = cfg.maxCapturedErrors ?? 5;
230
+ errorBuffer = [];
231
+ return;
232
+ }
116
233
  if (config.disableShakeGesture && (!config.quickIcon || config.quickIcon === 'auto')) {
117
234
  config.quickIcon = 'always';
118
235
  }
@@ -454,6 +571,15 @@ class YaverFeedback {
454
571
  return;
455
572
  await p2pClient.cancelRunnerBrowserAuth(sessionId);
456
573
  }
574
+ /** Submit the Claude paste-back verifier so the agent can finalise the
575
+ * OAuth handshake. RunnerAuthModal calls this after the user copies
576
+ * the code from platform.claude.com's callback page. */
577
+ static async submitRunnerBrowserAuthCode(sessionId, code) {
578
+ if (!p2pClient) {
579
+ throw new Error('Not connected to any agent.');
580
+ }
581
+ return p2pClient.submitRunnerBrowserAuthCode(sessionId, code);
582
+ }
457
583
  /**
458
584
  * Sign out: clear cached token + device, tear down the P2P client. The
459
585
  * SDK stays enabled; the next feedback trigger will re-prompt for login.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-feedback-react-native",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "Visual feedback SDK for Yaver \u2014 bug reports, screen recording, voice annotations, and local-first developer workflows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1251,7 +1251,15 @@ const RunnerAuthNativeModal: React.FC<{
1251
1251
  const [session, setSession] = useState<import('./types').RunnerBrowserAuthSession | null>(null);
1252
1252
  const [startError, setStartError] = useState<string | null>(null);
1253
1253
  const [copied, setCopied] = useState(false);
1254
+ const [pasteCode, setPasteCode] = useState('');
1255
+ const [submitting, setSubmitting] = useState(false);
1256
+ const [submitError, setSubmitError] = useState<string | null>(null);
1254
1257
  const startedRef = useRef(false);
1258
+ // Claude is the only runner that needs the user to paste a verifier
1259
+ // code back from platform.claude.com's callback page; Codex device-
1260
+ // auth and OpenCode (no OAuth at all) bypass this. Mirrors the
1261
+ // requiresPasteBack check in iOS YaverRunnerAuthFlowPane.swift.
1262
+ const needsPasteBack = runner === 'claude' || runner === 'claude-code';
1255
1263
 
1256
1264
  useEffect(() => {
1257
1265
  if (startedRef.current) return;
@@ -1380,9 +1388,92 @@ const RunnerAuthNativeModal: React.FC<{
1380
1388
  </Pressable>
1381
1389
  </View>
1382
1390
  ) : null}
1391
+ {needsPasteBack ? (
1392
+ <View style={{ marginTop: 14 }}>
1393
+ <Text style={runnerAuthModalStyles.codeLabel}>
1394
+ PASTE CODE FROM CLAUDE.COM
1395
+ </Text>
1396
+ <View style={{ flexDirection: 'row', gap: 8, marginTop: 6 }}>
1397
+ <View
1398
+ style={{
1399
+ flex: 1,
1400
+ backgroundColor: 'rgba(148,163,184,0.10)',
1401
+ borderRadius: 10,
1402
+ paddingHorizontal: 10,
1403
+ }}
1404
+ >
1405
+ {/* Lazy-import TextInput so the SDK doesn't pull
1406
+ extra surface from react-native at module load. */}
1407
+ {(() => {
1408
+ const { TextInput } = require('react-native');
1409
+ return (
1410
+ <TextInput
1411
+ value={pasteCode}
1412
+ onChangeText={(t: string) => {
1413
+ setPasteCode(t);
1414
+ setSubmitError(null);
1415
+ }}
1416
+ placeholder="paste code here"
1417
+ placeholderTextColor="#64748b"
1418
+ autoCapitalize="none"
1419
+ autoCorrect={false}
1420
+ spellCheck={false}
1421
+ style={{ color: '#f1f5f9', fontSize: 14, paddingVertical: 10 }}
1422
+ />
1423
+ );
1424
+ })()}
1425
+ </View>
1426
+ <Pressable
1427
+ disabled={!pasteCode.trim() || submitting}
1428
+ onPress={async () => {
1429
+ if (!session || !pasteCode.trim()) return;
1430
+ setSubmitting(true);
1431
+ setSubmitError(null);
1432
+ try {
1433
+ const next = await YaverFeedback.submitRunnerBrowserAuthCode(
1434
+ session.id,
1435
+ pasteCode.trim(),
1436
+ );
1437
+ setSession(next);
1438
+ setPasteCode('');
1439
+ } catch (err) {
1440
+ setSubmitError(err instanceof Error ? err.message : String(err));
1441
+ } finally {
1442
+ setSubmitting(false);
1443
+ }
1444
+ }}
1445
+ style={{
1446
+ paddingHorizontal: 14,
1447
+ justifyContent: 'center',
1448
+ backgroundColor:
1449
+ !pasteCode.trim() || submitting
1450
+ ? 'rgba(124,58,237,0.4)'
1451
+ : '#7c3aed',
1452
+ borderRadius: 10,
1453
+ }}
1454
+ >
1455
+ <Text style={{ color: 'white', fontWeight: '600' }}>
1456
+ {submitting ? '…' : 'Submit'}
1457
+ </Text>
1458
+ </Pressable>
1459
+ </View>
1460
+ {submitError ? (
1461
+ <Text
1462
+ style={{
1463
+ marginTop: 6,
1464
+ color: '#fca5a5',
1465
+ fontSize: 12,
1466
+ }}
1467
+ >
1468
+ {submitError}
1469
+ </Text>
1470
+ ) : null}
1471
+ </View>
1472
+ ) : null}
1383
1473
  <Text style={runnerAuthModalStyles.phishingHint}>
1384
- Device codes are a common phishing target. Never share this code. This dialog
1385
- turns green automatically once sign-in completes.
1474
+ {needsPasteBack
1475
+ ? 'After authorising on platform.claude.com, copy the code from the callback page and paste it above. Never share this code.'
1476
+ : 'Device codes are a common phishing target. Never share this code. This dialog turns green automatically once sign-in completes.'}
1386
1477
  </Text>
1387
1478
  </View>
1388
1479
  )}
package/src/P2PClient.ts CHANGED
@@ -199,6 +199,29 @@ export class P2PClient {
199
199
  try { await fetch(url, { method: 'POST', headers: this.authHeaders() }); } catch { /* best-effort */ }
200
200
  }
201
201
 
202
+ /** Submit the verifier code Anthropic shows on the callback page so
203
+ * the agent can finalise claude CLI's OAuth handshake. Codex doesn't
204
+ * use this — its device-auth flow auto-resolves via polling — but
205
+ * the SDK still exposes it for symmetry with mobile/src/components/
206
+ * RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
207
+ async submitRunnerBrowserAuthCode(
208
+ sessionId: string,
209
+ code: string,
210
+ ): Promise<RunnerBrowserAuthSession> {
211
+ const url = `${this.baseUrl}/runner-auth/browser/submit-code`;
212
+ const resp = await fetch(url, {
213
+ method: 'POST',
214
+ headers: { ...this.authHeaders(), 'Content-Type': 'application/json' },
215
+ body: JSON.stringify({ id: sessionId, code }),
216
+ });
217
+ if (!resp.ok) {
218
+ const text = await resp.text().catch(() => '');
219
+ throw new Error(`submitRunnerBrowserAuthCode HTTP ${resp.status}: ${text}`);
220
+ }
221
+ const data = await resp.json();
222
+ return data.session as RunnerBrowserAuthSession;
223
+ }
224
+
202
225
  async capabilitySnapshot(): Promise<CapabilitySnapshot | null> {
203
226
  try {
204
227
  const resp = await fetch(`${this.baseUrl}/capabilities/snapshot`, { headers: this.authHeaders() });
@@ -28,10 +28,13 @@ import {
28
28
  // (mobile/ios/Yaver/YaverInfo.{swift,m} + Android counterpart); a
29
29
  // standalone app bundled by its own developer has no such module.
30
30
  // When the SDK is loaded through Yaver's Hermes-push guest runtime we
31
- // deliberately no-op every public entry point Yaver owns the shake
32
- // gesture ("Reload / Back to Yaver" overlay), the feedback capture
33
- // flow, and the BlackBox streaming; running a second copy from inside
34
- // the guest just produces duplicate UIs and double P2P sessions.
31
+ // run in HOST MODE: dormant by default (no shake detector, no auto
32
+ // BlackBox, no QuickActionIcon Yaver's host shell owns those), but
33
+ // we DO register a DeviceEventEmitter listener so Yaver's overlay can
34
+ // flip the SDK live at runtime. When the user shakes inside the guest
35
+ // app and taps "Feedback" on the Yaver overlay, AppDelegate dispatches
36
+ // `yaverFeedback:startReport` into this bridge; the listener wakes the
37
+ // SDK and opens the modal in-place over the running guest UI.
35
38
  function isRunningInsideYaverHost(): boolean {
36
39
  try {
37
40
  return !!(NativeModules as any)?.YaverInfo;
@@ -40,11 +43,105 @@ function isRunningInsideYaverHost(): boolean {
40
43
  }
41
44
  }
42
45
 
43
- // Suppresses SDK activation when inside Yaver super-host. Callers of
44
- // YaverFeedback.init / startReport / startBatchRecording / … early-out
45
- // by checking this first so the SDK's side effects (accelerometer,
46
- // BlackBox HTTP, SSE command channel, FeedbackModal mount) never start.
47
- const YAVER_HOST_SUPPRESS = isRunningInsideYaverHost();
46
+ // Two distinct compile-time modes for a guest app like sfmg / talos:
47
+ //
48
+ // YAVER_HOST_MODE — bundled by Yaver's agent (/dev/build-native) for
49
+ // loading inside Yaver mobile. SDK code is in the
50
+ // bundle but boots PASSIVE: no shake detector, no
51
+ // auto-BlackBox, no container UI. Yaver's host
52
+ // overlay owns the shake gesture; when the user
53
+ // taps "Feedback" on Yaver's overlay, AppDelegate
54
+ // dispatches yaverFeedback:hostActivate into this
55
+ // bridge and the SDK runtime-flips active for a
56
+ // single feedback session.
57
+ //
58
+ // YAVER_SDK_MODE — sfmg's own standalone TestFlight / Play Store
59
+ // build, with the Yaver SDK embedded. SDK boots
60
+ // active: shake → modal directly, no Yaver host
61
+ // involved. Default for normal `expo build`.
62
+ //
63
+ // Both can be forced at build time via process.env. When neither is
64
+ // set, fall back to runtime detection: if the YaverInfo native module
65
+ // exists (we're inside Yaver), assume HOST_MODE; else SDK_MODE. This
66
+ // keeps older bundles (built before the agent learned to set the env)
67
+ // working unchanged.
68
+ const YAVER_HOST_MODE_BUILD = (() => {
69
+ try {
70
+ const v = (process.env as any)?.YAVER_HOST_MODE;
71
+ return v === 'true' || v === '1' || v === true || v === 1;
72
+ } catch { return false; }
73
+ })();
74
+ const YAVER_SDK_MODE_BUILD = (() => {
75
+ try {
76
+ const v = (process.env as any)?.YAVER_SDK_MODE;
77
+ return v === 'true' || v === '1' || v === true || v === 1;
78
+ } catch { return false; }
79
+ })();
80
+
81
+ // Effective mode after considering build flags AND runtime detection.
82
+ const IS_HOST_MODE =
83
+ YAVER_HOST_MODE_BUILD ||
84
+ (!YAVER_SDK_MODE_BUILD && isRunningInsideYaverHost());
85
+
86
+ // Tracks whether we've been runtime-activated by the host (sfmg-in-Yaver
87
+ // case). Independent of `enabled` so we can tell "host turned us on for
88
+ // one shot" apart from "developer toggled enabled programmatically".
89
+ let hostActivated = false;
90
+
91
+ // Host-activation listener. Always registered when in HOST mode so a
92
+ // Yaver overlay tap can wake the SDK even before the guest's
93
+ // YaverFeedback.init() runs. AppDelegate (mobile/ios/Yaver/AppDelegate.
94
+ // swift::handleFeedbackTap) sends `yaverFeedback:startReport` into the
95
+ // guest bridge when the user picks Feedback on the shake overlay.
96
+ //
97
+ // Activation flow:
98
+ // 1. Try Yaver's existing bearer (NativeModules.YaverInfo.
99
+ // inheritedAuthToken, populated by Yaver mobile's auth.ts on
100
+ // sign-in). Validate against /auth/validate before trusting.
101
+ // 2. If valid: setAuthToken + open feedback modal in-place (the
102
+ // modal already supports hot reload, screenshot, vibing chat).
103
+ // 3. If missing or invalid: open the SDK's own login screen.
104
+ if (IS_HOST_MODE) {
105
+ try {
106
+ const { DeviceEventEmitter } = require('react-native');
107
+ DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
108
+ hostActivated = true;
109
+ enabled = true;
110
+ void (async () => {
111
+ const yi = (NativeModules as any)?.YaverInfo;
112
+ const inheritedToken = String(yi?.inheritedAuthToken || '').trim();
113
+ const inheritedAgent = String(yi?.inheritedAgentUrl || '').trim();
114
+ const inheritedDevice = String(yi?.inheritedDeviceId || '').trim();
115
+ if (inheritedToken) {
116
+ // Lazy-import auth.ts so module load doesn't drag the auth
117
+ // network code into the active set when the SDK is dormant.
118
+ const { validateToken } = require('./auth');
119
+ const user = await validateToken(inheritedToken).catch(() => null);
120
+ if (user) {
121
+ // Seed config + connect the SDK to the host's auth.
122
+ if (!config) {
123
+ YaverFeedback.init({
124
+ authToken: inheritedToken,
125
+ agentUrl: inheritedAgent || undefined,
126
+ preferredDeviceId: inheritedDevice || undefined,
127
+ } as FeedbackConfig);
128
+ }
129
+ await YaverFeedback.setAuthToken(inheritedToken);
130
+ await YaverFeedback.startReport();
131
+ return;
132
+ }
133
+ }
134
+ // No token, or token invalid — fall through to the SDK's own
135
+ // login screen. The user picks an OAuth provider; on success
136
+ // the modal continues with the new token.
137
+ if (!config) {
138
+ YaverFeedback.init({} as FeedbackConfig);
139
+ }
140
+ YaverFeedback.showLogin();
141
+ })();
142
+ });
143
+ } catch { /* react-native unavailable in jsdom unit tests */ }
144
+ }
48
145
 
49
146
  let config: FeedbackConfig | null = null;
50
147
  let enabled = false;
@@ -126,17 +223,34 @@ export class YaverFeedback {
126
223
  * via `YaverDiscovery` on the first `startReport()` call.
127
224
  */
128
225
  static init(cfg: FeedbackConfig): void {
129
- if (YAVER_HOST_SUPPRESS) {
130
- // Running inside Yaver's super-host — yield to Yaver's native UX.
131
- enabled = false;
132
- return;
133
- }
134
226
  config = {
135
227
  trigger: 'shake',
136
228
  maxRecordingDuration: 120,
137
229
  autoLogin: true,
138
230
  ...cfg,
139
231
  };
232
+ if (IS_HOST_MODE) {
233
+ // sfmg / talos / etc. running inside Yaver mobile (compile-time
234
+ // YAVER_HOST_MODE or runtime-detected). Store config so a later
235
+ // host activation (yaverFeedback:startReport from AppDelegate)
236
+ // can open the modal — but skip the side effects Yaver's host
237
+ // shell owns: shake detector, auto-BlackBox, QuickActionIcon.
238
+ enabled = false;
239
+ // Configure auth endpoints + strict-native-auth even in passive
240
+ // mode so a host-activated session uses the same login routing
241
+ // the standalone path would.
242
+ configureAuthEndpoints({
243
+ convexSiteUrl: cfg.authConvexSiteUrl,
244
+ webBaseUrl: cfg.authWebBaseUrl,
245
+ });
246
+ setStrictNativeAuth(cfg.strictNativeAuth === true);
247
+ if (!config.convexUrl) {
248
+ config.convexUrl = cfg.authConvexSiteUrl ?? DEFAULT_CONVEX_SITE_URL;
249
+ }
250
+ maxErrors = cfg.maxCapturedErrors ?? 5;
251
+ errorBuffer = [];
252
+ return;
253
+ }
140
254
  if (config.disableShakeGesture && (!config.quickIcon || config.quickIcon === 'auto')) {
141
255
  config.quickIcon = 'always';
142
256
  }
@@ -482,6 +596,19 @@ export class YaverFeedback {
482
596
  await p2pClient.cancelRunnerBrowserAuth(sessionId);
483
597
  }
484
598
 
599
+ /** Submit the Claude paste-back verifier so the agent can finalise the
600
+ * OAuth handshake. RunnerAuthModal calls this after the user copies
601
+ * the code from platform.claude.com's callback page. */
602
+ static async submitRunnerBrowserAuthCode(
603
+ sessionId: string,
604
+ code: string,
605
+ ): Promise<import('./types').RunnerBrowserAuthSession> {
606
+ if (!p2pClient) {
607
+ throw new Error('Not connected to any agent.');
608
+ }
609
+ return p2pClient.submitRunnerBrowserAuthCode(sessionId, code);
610
+ }
611
+
485
612
  /**
486
613
  * Sign out: clear cached token + device, tear down the P2P client. The
487
614
  * SDK stays enabled; the next feedback trigger will re-prompt for login.