yaver-feedback-react-native 0.8.13 → 0.9.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.
@@ -45,6 +45,81 @@ const VibeChatScreen_1 = require("./VibeChatScreen");
45
45
  const DeployPanel_1 = require("./DeployPanel");
46
46
  const auth_1 = require("./auth");
47
47
  const preferences_1 = require("./preferences");
48
+ const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'];
49
+ function normalizeRunnerStatusRows(rows) {
50
+ const byId = new Map();
51
+ for (const row of rows) {
52
+ const raw = String(row.id || '').trim().toLowerCase();
53
+ if (!raw)
54
+ continue;
55
+ const normalized = raw === 'claude-code' ? 'claude' : raw;
56
+ if (!PRIMARY_RUNNER_IDS.includes(normalized))
57
+ continue;
58
+ byId.set(normalized, { ...row, id: normalized });
59
+ }
60
+ return PRIMARY_RUNNER_IDS.map((id) => {
61
+ const baseName = id === 'claude' ? 'Claude Code' : id === 'codex' ? 'OpenAI Codex' : 'OpenCode';
62
+ const row = byId.get(id);
63
+ if (!row) {
64
+ return {
65
+ id,
66
+ name: baseName,
67
+ installed: false,
68
+ authConfigured: false,
69
+ ready: false,
70
+ tone: 'warning',
71
+ statusLine: 'Not installed on the selected machine',
72
+ };
73
+ }
74
+ const versionPrefix = row.version?.trim() ? `${row.version.trim()} · ` : '';
75
+ const detail = row.error?.trim() || row.warning?.trim() || row.detail?.trim() || undefined;
76
+ if (!row.installed) {
77
+ return {
78
+ id,
79
+ name: row.name || baseName,
80
+ installed: false,
81
+ authConfigured: false,
82
+ ready: false,
83
+ version: row.version,
84
+ tone: 'warning',
85
+ statusLine: 'Not installed on the selected machine',
86
+ detail,
87
+ };
88
+ }
89
+ if (id === 'opencode') {
90
+ const configured = row.authConfigured || row.ready;
91
+ return {
92
+ id,
93
+ name: row.name || baseName,
94
+ installed: row.installed,
95
+ authConfigured: row.authConfigured,
96
+ ready: row.ready,
97
+ version: row.version,
98
+ tone: configured ? 'ok' : 'warning',
99
+ statusLine: configured
100
+ ? `${versionPrefix}Configured on the selected machine`
101
+ : `${versionPrefix}Needs provider config on the selected machine`,
102
+ detail,
103
+ };
104
+ }
105
+ const authed = row.authConfigured || row.ready;
106
+ return {
107
+ id,
108
+ name: row.name || baseName,
109
+ installed: row.installed,
110
+ authConfigured: row.authConfigured,
111
+ ready: row.ready,
112
+ version: row.version,
113
+ tone: authed ? 'ok' : 'warning',
114
+ statusLine: authed
115
+ ? `${versionPrefix}Signed in on the selected machine`
116
+ : `${versionPrefix}Not signed in on the selected machine`,
117
+ detail,
118
+ actionLabel: authed ? 'Re-auth' : 'Sign in',
119
+ actionRunner: id,
120
+ };
121
+ });
122
+ }
48
123
  const FeedbackModal = () => {
49
124
  const { width: winW, height: winH } = (0, react_native_1.useWindowDimensions)();
50
125
  const isTablet = Math.min(winW, winH) >= 600;
@@ -81,6 +156,12 @@ const FeedbackModal = () => {
81
156
  title: 'No machine selected',
82
157
  detail: 'Pick a remote dev machine before using the feedback actions.',
83
158
  });
159
+ const [runnerCards, setRunnerCards] = (0, react_1.useState)(() => normalizeRunnerStatusRows([]));
160
+ const [runnerStatusLoading, setRunnerStatusLoading] = (0, react_1.useState)(false);
161
+ const [runnerStatusError, setRunnerStatusError] = (0, react_1.useState)(null);
162
+ const [preferredRunner, setPreferredRunnerState] = (0, react_1.useState)(null);
163
+ const [preferredModel, setPreferredModelState] = (0, react_1.useState)('');
164
+ const [showOpenCodeConfig, setShowOpenCodeConfig] = (0, react_1.useState)(false);
84
165
  const mountedRef = (0, react_1.useRef)(true);
85
166
  const loadSelectedMachine = (0, react_1.useCallback)(async () => {
86
167
  const cfg = YaverFeedback_1.YaverFeedback.getConfig();
@@ -178,6 +259,72 @@ const FeedbackModal = () => {
178
259
  }
179
260
  }
180
261
  }, []);
262
+ const loadRunnerStatuses = (0, react_1.useCallback)(async () => {
263
+ const cfg = YaverFeedback_1.YaverFeedback.getConfig();
264
+ if (!cfg?.authToken) {
265
+ if (mountedRef.current) {
266
+ setRunnerCards(normalizeRunnerStatusRows([]));
267
+ setRunnerStatusError('Sign in to inspect coding-agent status.');
268
+ setRunnerStatusLoading(false);
269
+ }
270
+ return;
271
+ }
272
+ if (!cfg.preferredDeviceId) {
273
+ if (mountedRef.current) {
274
+ setRunnerCards(normalizeRunnerStatusRows([]));
275
+ setRunnerStatusError('Pick a machine to inspect coding-agent status.');
276
+ setRunnerStatusLoading(false);
277
+ }
278
+ return;
279
+ }
280
+ if (mountedRef.current) {
281
+ setRunnerStatusLoading(true);
282
+ setRunnerStatusError(null);
283
+ }
284
+ try {
285
+ let client = YaverFeedback_1.YaverFeedback.getP2PClient();
286
+ if (!client) {
287
+ const ok = await YaverFeedback_1.YaverFeedback.reconnect();
288
+ if (ok)
289
+ client = YaverFeedback_1.YaverFeedback.getP2PClient();
290
+ }
291
+ if (!client) {
292
+ throw new Error('Not connected to the selected machine yet.');
293
+ }
294
+ const rows = await client.getRunnerAuthStatus();
295
+ if (mountedRef.current) {
296
+ setRunnerCards(normalizeRunnerStatusRows(rows));
297
+ }
298
+ }
299
+ catch (err) {
300
+ if (mountedRef.current) {
301
+ setRunnerCards(normalizeRunnerStatusRows([]));
302
+ setRunnerStatusError(err instanceof Error ? err.message : String(err));
303
+ }
304
+ }
305
+ finally {
306
+ if (mountedRef.current)
307
+ setRunnerStatusLoading(false);
308
+ }
309
+ }, []);
310
+ const loadRoutingPrefs = (0, react_1.useCallback)(async () => {
311
+ try {
312
+ const [runner, model] = await Promise.all([
313
+ (0, preferences_1.getPreferredRunner)(),
314
+ (0, preferences_1.getPreferredModel)(),
315
+ ]);
316
+ if (!mountedRef.current)
317
+ return;
318
+ setPreferredRunnerState(runner);
319
+ setPreferredModelState(model ?? '');
320
+ }
321
+ catch {
322
+ if (!mountedRef.current)
323
+ return;
324
+ setPreferredRunnerState(null);
325
+ setPreferredModelState('');
326
+ }
327
+ }, []);
181
328
  (0, react_1.useEffect)(() => {
182
329
  mountedRef.current = true;
183
330
  const sub = react_native_1.DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
@@ -205,6 +352,7 @@ const FeedbackModal = () => {
205
352
  })
206
353
  .catch(() => { });
207
354
  void loadSelectedMachine();
355
+ void loadRunnerStatuses();
208
356
  }
209
357
  });
210
358
  // Agent streams build / compile progress through the BlackBox
@@ -232,15 +380,16 @@ const FeedbackModal = () => {
232
380
  sub.remove();
233
381
  statusSub.remove();
234
382
  };
235
- }, [loadSelectedMachine]);
383
+ }, [loadRunnerStatuses, loadSelectedMachine]);
236
384
  (0, react_1.useEffect)(() => {
237
385
  if (!visible)
238
386
  return;
239
387
  const interval = setInterval(() => {
240
388
  void loadSelectedMachine();
389
+ void loadRunnerStatuses();
241
390
  }, 5000);
242
391
  return () => clearInterval(interval);
243
- }, [loadSelectedMachine, visible]);
392
+ }, [loadRunnerStatuses, loadSelectedMachine, visible]);
244
393
  (0, react_1.useEffect)(() => {
245
394
  if (!visible) {
246
395
  setKeyboardInset(0);
@@ -273,6 +422,7 @@ const FeedbackModal = () => {
273
422
  setAction('idle');
274
423
  setShowVibeInput(false);
275
424
  setVibePrompt('');
425
+ setRunnerStatusError(null);
276
426
  }, []);
277
427
  // Helper: run a P2P call; on network failure, ask YaverFeedback to
278
428
  // re-query Convex for the fresh IP and retry once. Solves the common
@@ -557,7 +707,13 @@ const FeedbackModal = () => {
557
707
  setLastVibeTaskId(result.taskId);
558
708
  // Hand off to VibeChatScreen — it streams the SSE transcript,
559
709
  // accepts follow-ups, and surfaces a Reload button.
560
- setActiveVibe({ taskId: result.taskId, initialPrompt: promptText });
710
+ setActiveVibe({
711
+ taskId: result.taskId,
712
+ initialPrompt: promptText,
713
+ project: identity.projectName,
714
+ runner: preferredRunner ?? undefined,
715
+ model: preferredModel ?? undefined,
716
+ });
561
717
  setVibePrompt('');
562
718
  setShowVibeInput(false);
563
719
  }
@@ -575,6 +731,9 @@ const FeedbackModal = () => {
575
731
  }, [closeSoon, isRecordingVideo, lastVideo]);
576
732
  */
577
733
  const busy = action !== 'idle';
734
+ const readyRunnerCount = runnerCards.filter((row) => row.ready || row.authConfigured).length;
735
+ const missingRunnerCount = runnerCards.filter((row) => !row.installed).length;
736
+ const needsAuthRunnerCount = runnerCards.filter((row) => row.installed && !row.authConfigured && !row.ready).length;
578
737
  // Once the user fires off a vibe task, swap the entire modal body
579
738
  // for the live chat screen. The chat manages its own SSE
580
739
  // subscription, multi-turn follow-ups, and Reload button. Closing
@@ -585,7 +744,7 @@ const FeedbackModal = () => {
585
744
  <AuthOverlay_1.AuthOverlay />
586
745
  <QuickActionIcon_1.QuickActionIcon />
587
746
  <react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={() => setActiveVibe(null)}>
588
- {client ? (<VibeChatScreen_1.VibeChatScreen client={client} initialTaskId={activeVibe.taskId} initialUserPrompt={activeVibe.initialPrompt} onClose={() => setActiveVibe(null)} onReload={async () => {
747
+ {client ? (<VibeChatScreen_1.VibeChatScreen client={client} initialTaskId={activeVibe.taskId} initialUserPrompt={activeVibe.initialPrompt} project={activeVibe.project} runner={activeVibe.runner} model={activeVibe.model} onClose={() => setActiveVibe(null)} onReload={async () => {
589
748
  const c = YaverFeedback_1.YaverFeedback.getP2PClient();
590
749
  if (!c)
591
750
  throw new Error('Not connected');
@@ -664,6 +823,57 @@ const FeedbackModal = () => {
664
823
  <react_native_1.Text style={styles.machineMeta}>{machineCard.detail}</react_native_1.Text>
665
824
  </react_native_1.Pressable>
666
825
 
826
+ <react_native_1.View style={styles.runnerSection}>
827
+ <react_native_1.View style={styles.runnerSectionHeader}>
828
+ <react_native_1.View style={{ flex: 1 }}>
829
+ <react_native_1.Text style={styles.runnerSectionTitle}>Coding Agents</react_native_1.Text>
830
+ <react_native_1.Text style={styles.runnerSectionSubtitle}>
831
+ {runnerStatusLoading
832
+ ? 'Refreshing runner status on the selected machine…'
833
+ : `${readyRunnerCount} ready · ${needsAuthRunnerCount} need sign-in · ${missingRunnerCount} missing`}
834
+ </react_native_1.Text>
835
+ </react_native_1.View>
836
+ <react_native_1.Pressable onPress={() => void loadRunnerStatuses()} style={({ pressed }) => [
837
+ styles.runnerRefreshBtn,
838
+ pressed && styles.buttonPressed,
839
+ ]} accessibilityRole="button" accessibilityLabel="Refresh coding-agent status">
840
+ <react_native_1.Text style={styles.runnerRefreshBtnText}>
841
+ {runnerStatusLoading ? 'Refreshing…' : 'Refresh'}
842
+ </react_native_1.Text>
843
+ </react_native_1.Pressable>
844
+ </react_native_1.View>
845
+
846
+ {runnerCards.map((row) => (<react_native_1.View key={row.id} style={[
847
+ styles.runnerCard,
848
+ row.tone === 'ok' && styles.runnerCardOk,
849
+ row.tone === 'warning' && styles.runnerCardWarning,
850
+ row.tone === 'error' && styles.runnerCardError,
851
+ ]}>
852
+ <react_native_1.View style={styles.runnerCardTop}>
853
+ <react_native_1.View style={{ flex: 1 }}>
854
+ <react_native_1.Text style={styles.runnerCardTitle}>{row.name}</react_native_1.Text>
855
+ <react_native_1.Text style={[
856
+ styles.runnerCardStatus,
857
+ row.tone === 'ok' && styles.runnerCardStatusOk,
858
+ row.tone === 'warning' && styles.runnerCardStatusWarning,
859
+ row.tone === 'error' && styles.runnerCardStatusError,
860
+ ]}>
861
+ {row.statusLine}
862
+ </react_native_1.Text>
863
+ </react_native_1.View>
864
+ {row.actionRunner ? (<react_native_1.Pressable onPress={() => setRunnerAuthModal(row.actionRunner ?? null)} style={({ pressed }) => [
865
+ styles.runnerActionBtn,
866
+ pressed && styles.buttonPressed,
867
+ ]} accessibilityRole="button" accessibilityLabel={`${row.actionLabel} ${row.name}`}>
868
+ <react_native_1.Text style={styles.runnerActionBtnText}>{row.actionLabel}</react_native_1.Text>
869
+ </react_native_1.Pressable>) : null}
870
+ </react_native_1.View>
871
+ {row.detail ? (<react_native_1.Text style={styles.runnerCardDetail}>{row.detail}</react_native_1.Text>) : null}
872
+ </react_native_1.View>))}
873
+
874
+ {runnerStatusError ? (<react_native_1.Text style={styles.runnerSectionError}>{runnerStatusError}</react_native_1.Text>) : null}
875
+ </react_native_1.View>
876
+
667
877
  {quickIconHidden && (<react_native_1.View style={styles.quickIconNote}>
668
878
  <react_native_1.Text style={styles.quickIconNoteText}>
669
879
  Quick access icon is hidden. Shake the phone if you want feedback back fast.
@@ -755,29 +965,6 @@ const FeedbackModal = () => {
755
965
  platform smarts here. */}
756
966
  {!showDeploy ? (<ActionRow label="Deploy" tint="#7f8cf7" onPress={() => setShowDeploy(true)} disabled={busy}/>) : (<DeployPanel_1.DeployPanel onClose={() => setShowDeploy(false)}/>)}
757
967
 
758
- {/* Remote sign-in buttons — trigger codex/claude device-auth
759
- on the selected agent without leaving the app. Opens a
760
- small native modal showing the verification URL + 8-char
761
- code the user enters in any browser. No API keys. */}
762
- <react_native_1.View style={runnerAuthRowStyles.container}>
763
- <react_native_1.Pressable onPress={() => setRunnerAuthModal('codex')} disabled={busy} style={({ pressed }) => [
764
- runnerAuthRowStyles.button,
765
- pressed && runnerAuthRowStyles.buttonPressed,
766
- busy && runnerAuthRowStyles.buttonDisabled,
767
- ]} accessibilityRole="button" accessibilityLabel="Remote sign-in Codex">
768
- <react_native_1.Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</react_native_1.Text>
769
- <react_native_1.Text style={runnerAuthRowStyles.buttonName}>Codex</react_native_1.Text>
770
- </react_native_1.Pressable>
771
- <react_native_1.Pressable onPress={() => setRunnerAuthModal('claude')} disabled={busy} style={({ pressed }) => [
772
- runnerAuthRowStyles.button,
773
- pressed && runnerAuthRowStyles.buttonPressed,
774
- busy && runnerAuthRowStyles.buttonDisabled,
775
- ]} accessibilityRole="button" accessibilityLabel="Remote sign-in Claude">
776
- <react_native_1.Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</react_native_1.Text>
777
- <react_native_1.Text style={runnerAuthRowStyles.buttonName}>Claude</react_native_1.Text>
778
- </react_native_1.Pressable>
779
- </react_native_1.View>
780
-
781
968
  {progress !== null && (<react_native_1.View style={styles.progressTrack}>
782
969
  <react_native_1.View style={[
783
970
  styles.progressFill,
@@ -798,7 +985,10 @@ const FeedbackModal = () => {
798
985
  </react_native_1.KeyboardAvoidingView>
799
986
  </react_native_1.Pressable>
800
987
  </react_native_1.Modal>)}
801
- {runnerAuthModal ? (<RunnerAuthNativeModal runner={runnerAuthModal} onClose={() => setRunnerAuthModal(null)}/>) : null}
988
+ {runnerAuthModal ? (<RunnerAuthNativeModal runner={runnerAuthModal} onClose={() => {
989
+ setRunnerAuthModal(null);
990
+ void loadRunnerStatuses();
991
+ }}/>) : null}
802
992
  </>);
803
993
  };
804
994
  exports.FeedbackModal = FeedbackModal;
@@ -996,6 +1186,106 @@ const styles = react_native_1.StyleSheet.create({
996
1186
  marginTop: 4,
997
1187
  lineHeight: 17,
998
1188
  },
1189
+ runnerSection: {
1190
+ marginTop: 2,
1191
+ gap: 10,
1192
+ },
1193
+ runnerSectionHeader: {
1194
+ flexDirection: 'row',
1195
+ alignItems: 'center',
1196
+ gap: 10,
1197
+ },
1198
+ runnerSectionTitle: {
1199
+ color: '#f8fafc',
1200
+ fontSize: 16,
1201
+ fontWeight: '700',
1202
+ },
1203
+ runnerSectionSubtitle: {
1204
+ marginTop: 2,
1205
+ color: '#94a3b8',
1206
+ fontSize: 12,
1207
+ },
1208
+ runnerRefreshBtn: {
1209
+ borderRadius: 10,
1210
+ borderWidth: 1,
1211
+ borderColor: 'rgba(148,163,184,0.22)',
1212
+ backgroundColor: 'rgba(15,23,42,0.65)',
1213
+ paddingHorizontal: 10,
1214
+ paddingVertical: 8,
1215
+ },
1216
+ runnerRefreshBtnText: {
1217
+ color: '#cbd5e1',
1218
+ fontSize: 12,
1219
+ fontWeight: '600',
1220
+ },
1221
+ runnerCard: {
1222
+ borderRadius: 12,
1223
+ borderWidth: 1,
1224
+ borderColor: 'rgba(148,163,184,0.14)',
1225
+ backgroundColor: 'rgba(15,23,42,0.45)',
1226
+ paddingHorizontal: 12,
1227
+ paddingVertical: 11,
1228
+ gap: 6,
1229
+ },
1230
+ runnerCardOk: {
1231
+ borderColor: 'rgba(34,197,94,0.28)',
1232
+ backgroundColor: 'rgba(20,83,45,0.20)',
1233
+ },
1234
+ runnerCardWarning: {
1235
+ borderColor: 'rgba(251,191,36,0.28)',
1236
+ backgroundColor: 'rgba(120,53,15,0.18)',
1237
+ },
1238
+ runnerCardError: {
1239
+ borderColor: 'rgba(248,113,113,0.28)',
1240
+ backgroundColor: 'rgba(127,29,29,0.18)',
1241
+ },
1242
+ runnerCardTop: {
1243
+ flexDirection: 'row',
1244
+ alignItems: 'center',
1245
+ gap: 10,
1246
+ },
1247
+ runnerCardTitle: {
1248
+ color: '#f8fafc',
1249
+ fontSize: 14,
1250
+ fontWeight: '700',
1251
+ },
1252
+ runnerCardStatus: {
1253
+ marginTop: 2,
1254
+ fontSize: 12,
1255
+ color: '#cbd5e1',
1256
+ },
1257
+ runnerCardStatusOk: {
1258
+ color: '#86efac',
1259
+ },
1260
+ runnerCardStatusWarning: {
1261
+ color: '#fcd34d',
1262
+ },
1263
+ runnerCardStatusError: {
1264
+ color: '#fca5a5',
1265
+ },
1266
+ runnerCardDetail: {
1267
+ color: '#94a3b8',
1268
+ fontSize: 11,
1269
+ lineHeight: 16,
1270
+ },
1271
+ runnerActionBtn: {
1272
+ borderRadius: 10,
1273
+ borderWidth: 1,
1274
+ borderColor: 'rgba(129,140,248,0.35)',
1275
+ backgroundColor: 'rgba(67,56,202,0.22)',
1276
+ paddingHorizontal: 12,
1277
+ paddingVertical: 8,
1278
+ },
1279
+ runnerActionBtnText: {
1280
+ color: '#c7d2fe',
1281
+ fontSize: 12,
1282
+ fontWeight: '700',
1283
+ },
1284
+ runnerSectionError: {
1285
+ color: '#fca5a5',
1286
+ fontSize: 12,
1287
+ lineHeight: 18,
1288
+ },
999
1289
  captureChoices: {
1000
1290
  gap: 10,
1001
1291
  },
@@ -1328,39 +1618,6 @@ const RunnerAuthNativeModal = ({ runner, onClose }) => {
1328
1618
  </react_native_1.View>
1329
1619
  </react_native_1.Modal>);
1330
1620
  };
1331
- const runnerAuthRowStyles = react_native_1.StyleSheet.create({
1332
- container: {
1333
- flexDirection: 'row',
1334
- gap: 8,
1335
- marginTop: 8,
1336
- flexWrap: 'wrap',
1337
- },
1338
- button: {
1339
- flexGrow: 1,
1340
- flexBasis: 0,
1341
- minWidth: 120,
1342
- paddingHorizontal: 12,
1343
- paddingVertical: 10,
1344
- borderRadius: 10,
1345
- borderWidth: 1,
1346
- borderColor: 'rgba(148,163,184,0.22)',
1347
- backgroundColor: 'rgba(15,23,42,0.6)',
1348
- },
1349
- buttonPressed: { opacity: 0.7 },
1350
- buttonDisabled: { opacity: 0.4 },
1351
- buttonLabel: {
1352
- fontSize: 10,
1353
- color: '#94a3b8',
1354
- textTransform: 'uppercase',
1355
- letterSpacing: 0.8,
1356
- },
1357
- buttonName: {
1358
- marginTop: 2,
1359
- fontSize: 14,
1360
- fontWeight: '600',
1361
- color: '#f1f5f9',
1362
- },
1363
- });
1364
1621
  const runnerAuthModalStyles = react_native_1.StyleSheet.create({
1365
1622
  overlay: {
1366
1623
  flex: 1,
@@ -104,7 +104,14 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
104
104
  return;
105
105
  }
106
106
  const direct = await (0, auth_1.probeDeviceReachability)(device);
107
- if (!direct.reachable && !device.needsAuth) {
107
+ // Do not hard-block selection just because the LAN /health probe
108
+ // failed. The standalone SDK can still reach a healthy machine via
109
+ // the normal selected-device discovery path (including relay), and
110
+ // the Yaver host path may already be proving the machine works.
111
+ // Only treat the machine as unpickable when BOTH:
112
+ // 1. Convex says it is offline, and
113
+ // 2. the direct probe also failed.
114
+ if (!device.isOnline && !direct.reachable && !device.needsAuth) {
108
115
  setError('Selected machine is not responding. Start `yaver serve` on it and try again.');
109
116
  setReachability((prev) => ({ ...prev, [device.deviceId]: direct }));
110
117
  return;
@@ -131,9 +138,11 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
131
138
  ? '#f59e0b'
132
139
  : effectivelyReachable
133
140
  ? '#22c55e'
134
- : explicitlyOffline || !device.isOnline
135
- ? '#ef4444'
136
- : '#22c55e';
141
+ : device.isOnline
142
+ ? '#f59e0b'
143
+ : explicitlyOffline || !device.isOnline
144
+ ? '#ef4444'
145
+ : '#22c55e';
137
146
  // Derive a single short status phrase the user can act on.
138
147
  let statusLine = device.platform;
139
148
  if (probe === undefined) {
@@ -150,7 +159,7 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
150
159
  'Needs pairing — open the Yaver app to adopt this machine';
151
160
  }
152
161
  else if (explicitlyOffline) {
153
- statusLine = 'Agent not responding on this machine';
162
+ statusLine = 'Online, but direct probe failed — relay / selected-machine path may still work';
154
163
  }
155
164
  else if (device.runnerDown) {
156
165
  statusLine = 'Runner down — restart the coding agent on the Mac';
@@ -1,4 +1,4 @@
1
- import { CapabilitySnapshot, FeedbackBundle, IncidentEvent, OperationState, RunnerBrowserAuthSession, TestSession, VoiceCapability } from './types';
1
+ import { CapabilitySnapshot, FeedbackBundle, IncidentEvent, OpenCodeConfigSummary, OperationState, RunnerBrowserAuthSession, RunnerAuthStatusRow, TestSession, VoiceCapability } from './types';
2
2
  export interface FeedbackEvent {
3
3
  type: string;
4
4
  timestamp: string;
@@ -51,6 +51,16 @@ export declare class P2PClient {
51
51
  setAuthToken(token: string): void;
52
52
  /** Update the relay password (used for managed-relay baseUrls). */
53
53
  setRelayPassword(password: string): void;
54
+ /** Read-only base URL — used by the voice vibe-coding path to probe
55
+ * GET /voice/status before opening the stream. */
56
+ get agentBaseUrl(): string;
57
+ /** WebSocket URL for the agent's voice stream (WS /voice/stream). The
58
+ * voice vibe-coding loop streams mic audio here and receives the
59
+ * transcript + agent task + TTS frames back. */
60
+ voiceStreamUrl(): string;
61
+ /** Auth headers for the voice WS + status probe — same bearer (and
62
+ * relay password) as every other agent request. */
63
+ voiceAuthHeaders(): Record<string, string>;
54
64
  /** Merge in Authorization + (optional) X-Relay-Password on top of a header block. */
55
65
  private authHeaders;
56
66
  /**
@@ -69,6 +79,26 @@ export declare class P2PClient {
69
79
  * the SDK still exposes it for symmetry with mobile/src/components/
70
80
  * RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
71
81
  submitRunnerBrowserAuthCode(sessionId: string, code: string): Promise<RunnerBrowserAuthSession>;
82
+ getRunnerAuthStatus(): Promise<RunnerAuthStatusRow[]>;
83
+ getOpenCodeConfig(): Promise<OpenCodeConfigSummary | null>;
84
+ saveOpenCodeConfig(patch: {
85
+ defaultAgent?: string;
86
+ model?: string;
87
+ smallModel?: string;
88
+ buildModel?: string;
89
+ planModel?: string;
90
+ providers?: Array<{
91
+ id: string;
92
+ name?: string;
93
+ baseUrl?: string;
94
+ apiKey?: string;
95
+ delete?: boolean;
96
+ }>;
97
+ }): Promise<{
98
+ ok: boolean;
99
+ config?: OpenCodeConfigSummary;
100
+ error?: string;
101
+ }>;
72
102
  capabilitySnapshot(): Promise<CapabilitySnapshot | null>;
73
103
  incidents(opts?: {
74
104
  category?: string;
package/dist/P2PClient.js CHANGED
@@ -143,6 +143,22 @@ class P2PClient {
143
143
  setRelayPassword(password) {
144
144
  this.relayPassword = password;
145
145
  }
146
+ /** Read-only base URL — used by the voice vibe-coding path to probe
147
+ * GET /voice/status before opening the stream. */
148
+ get agentBaseUrl() {
149
+ return this.baseUrl;
150
+ }
151
+ /** WebSocket URL for the agent's voice stream (WS /voice/stream). The
152
+ * voice vibe-coding loop streams mic audio here and receives the
153
+ * transcript + agent task + TTS frames back. */
154
+ voiceStreamUrl() {
155
+ return this.baseUrl.replace(/^http/, 'ws') + '/voice/stream';
156
+ }
157
+ /** Auth headers for the voice WS + status probe — same bearer (and
158
+ * relay password) as every other agent request. */
159
+ voiceAuthHeaders() {
160
+ return this.authHeaders();
161
+ }
146
162
  /** Merge in Authorization + (optional) X-Relay-Password on top of a header block. */
147
163
  authHeaders(extra = {}) {
148
164
  const h = { ...extra };
@@ -208,6 +224,45 @@ class P2PClient {
208
224
  const data = await resp.json();
209
225
  return data.session;
210
226
  }
227
+ async getRunnerAuthStatus() {
228
+ const resp = await fetch(`${this.baseUrl}/runner-auth/status`, {
229
+ headers: this.authHeaders(),
230
+ });
231
+ if (!resp.ok) {
232
+ const text = await resp.text().catch(() => '');
233
+ throw new Error(`getRunnerAuthStatus HTTP ${resp.status}: ${text}`);
234
+ }
235
+ const data = await resp.json().catch(() => ({}));
236
+ return Array.isArray(data.runners) ? data.runners : [];
237
+ }
238
+ async getOpenCodeConfig() {
239
+ const resp = await fetch(`${this.baseUrl}/runner/opencode/config`, {
240
+ headers: this.authHeaders(),
241
+ });
242
+ if (!resp.ok) {
243
+ const text = await resp.text().catch(() => '');
244
+ throw new Error(`getOpenCodeConfig HTTP ${resp.status}: ${text}`);
245
+ }
246
+ const data = await resp.json().catch(() => ({}));
247
+ return (data.config ?? null);
248
+ }
249
+ async saveOpenCodeConfig(patch) {
250
+ try {
251
+ const resp = await fetch(`${this.baseUrl}/runner/opencode/config`, {
252
+ method: 'POST',
253
+ headers: { ...this.authHeaders(), 'Content-Type': 'application/json' },
254
+ body: JSON.stringify(patch),
255
+ });
256
+ const data = await resp.json().catch(() => ({}));
257
+ if (!resp.ok) {
258
+ return { ok: false, error: data.error || `HTTP ${resp.status}` };
259
+ }
260
+ return { ok: true, config: data.config };
261
+ }
262
+ catch (err) {
263
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
264
+ }
265
+ }
211
266
  async capabilitySnapshot() {
212
267
  try {
213
268
  const resp = await fetch(`${this.baseUrl}/capabilities/snapshot`, { headers: this.authHeaders() });
@@ -390,6 +445,10 @@ class P2PClient {
390
445
  s2sReady: data.s2sReady ?? false,
391
446
  sttProvider: data.sttProvider ?? undefined,
392
447
  sttReady: data.sttReady ?? false,
448
+ ttsProvider: data.ttsProvider ?? undefined,
449
+ ttsReady: data.ttsReady ?? false,
450
+ enabled: data.enabled ?? false,
451
+ defaultProject: data.defaultProject ?? undefined,
393
452
  };
394
453
  }
395
454
  /**
@@ -15,6 +15,11 @@ interface Props {
15
15
  /** Called when the user taps Reload after a task completes — uses
16
16
  * P2PClient.reloadApp() with the active project context. */
17
17
  onReload?: () => Promise<void>;
18
+ /** Optional context forwarded to the voice stream so the agent runs
19
+ * the task against the right project / runner / model. */
20
+ project?: string;
21
+ model?: string;
22
+ runner?: string;
18
23
  }
19
- export declare function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, }: Props): React.JSX.Element;
24
+ export declare function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, project, model, runner, }: Props): React.JSX.Element;
20
25
  export {};