yaver-feedback-react-native 0.8.12 → 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.
@@ -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;
@@ -12,6 +12,22 @@ export interface ReloadAck {
12
12
  nativeChangesDetected?: boolean;
13
13
  changeClass?: string;
14
14
  }
15
+ /**
16
+ * Try to resolve `{projectName, bundleId}` for the running app so the
17
+ * agent can map the reload request to a MobileProject in its scan
18
+ * cache. Order: caller-supplied opts → Expo Constants → react-native
19
+ * NativeModules. None of the lookups throw — missing data just means
20
+ * the agent will fall back to its own dev-server resolution.
21
+ */
22
+ export declare function resolveAppIdentity(opts?: {
23
+ projectName?: string;
24
+ bundleId?: string;
25
+ projectPath?: string;
26
+ }): {
27
+ projectName?: string;
28
+ bundleId?: string;
29
+ projectPath?: string;
30
+ };
15
31
  /**
16
32
  * Lightweight P2P HTTP client for communicating with a Yaver agent.
17
33
  *
@@ -35,6 +51,16 @@ export declare class P2PClient {
35
51
  setAuthToken(token: string): void;
36
52
  /** Update the relay password (used for managed-relay baseUrls). */
37
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>;
38
64
  /** Merge in Authorization + (optional) X-Relay-Password on top of a header block. */
39
65
  private authHeaders;
40
66
  /**
@@ -53,6 +79,26 @@ export declare class P2PClient {
53
79
  * the SDK still exposes it for symmetry with mobile/src/components/
54
80
  * RunnerAuthModal.tsx and the Swift YaverRunnerAuthFlowPane. */
55
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
+ }>;
56
102
  capabilitySnapshot(): Promise<CapabilitySnapshot | null>;
57
103
  incidents(opts?: {
58
104
  category?: string;
@@ -227,5 +273,60 @@ export declare class P2PClient {
227
273
  timestamp?: number;
228
274
  }): Promise<boolean>;
229
275
  /** Internal helper for authenticated GET/POST requests. */
276
+ /**
277
+ * Convergence point for ALL feedback surfaces — Tasks tab, in-Yaver
278
+ * native pane, and this standalone SDK all POST the same shape to
279
+ * `/tasks`. Wraps the user's text with the shared prompt builder
280
+ * (see `_core/buildFeedbackPrompt`) so every surface conditions
281
+ * the AI the same way.
282
+ *
283
+ * Returns the agent's response payload (`taskId`, etc.) so callers
284
+ * can wire `streamTaskOutput()` next for live transcript.
285
+ *
286
+ * Inputs:
287
+ * - userPrompt what the user typed
288
+ * - projectName / path optional Hot-Reload project context
289
+ * - runner / model optional preferred coding agent + model
290
+ * - screenshotBase64 optional JPEG base64 (no `data:` prefix)
291
+ * - imageMimeType defaults to "image/jpeg"
292
+ */
293
+ createFeedbackTask(input: {
294
+ userPrompt: string;
295
+ projectName?: string;
296
+ projectPath?: string;
297
+ runner?: string;
298
+ model?: string;
299
+ screenshotBase64?: string;
300
+ imageMimeType?: string;
301
+ }): Promise<{
302
+ taskId: string;
303
+ raw?: unknown;
304
+ }>;
305
+ /**
306
+ * Subscribe to a task's live stdout/stderr stream. Returns an abort
307
+ * function — call it to detach. The agent emits NDJSON lines on
308
+ * `/tasks/{id}/output`; we surface each line via `onLine`.
309
+ *
310
+ * `onComplete` fires when the agent reports the task entered a
311
+ * terminal status (completed / failed / stopped). After that the
312
+ * caller should stop calling abort().
313
+ *
314
+ * Robust to fetch streaming on Hermes (streams Body via Response.
315
+ * body.getReader on platforms that support it; falls back to
316
+ * polling `/tasks/{id}` every 750 ms if streaming isn't available).
317
+ */
318
+ streamTaskOutput(taskId: string, onLine: (line: string) => void, onComplete: (status: string) => void): () => void;
319
+ /**
320
+ * Send a follow-up message into an existing task — multi-turn vibe
321
+ * chat. The agent's `/tasks/{id}/resume` accepts the same shape as
322
+ * `/tasks` (description / userPrompt / images), and the existing
323
+ * task picks back up with the same runner + project context.
324
+ */
325
+ resumeTask(input: {
326
+ taskId: string;
327
+ userPrompt: string;
328
+ screenshotBase64?: string;
329
+ imageMimeType?: string;
330
+ }): Promise<void>;
230
331
  private request;
231
332
  }
package/dist/P2PClient.js CHANGED
@@ -1,6 +1,40 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.P2PClient = void 0;
37
+ exports.resolveAppIdentity = resolveAppIdentity;
4
38
  const react_native_1 = require("react-native");
5
39
  /**
6
40
  * Try to resolve `{projectName, bundleId}` for the running app so the
@@ -109,6 +143,22 @@ class P2PClient {
109
143
  setRelayPassword(password) {
110
144
  this.relayPassword = password;
111
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
+ }
112
162
  /** Merge in Authorization + (optional) X-Relay-Password on top of a header block. */
113
163
  authHeaders(extra = {}) {
114
164
  const h = { ...extra };
@@ -174,6 +224,45 @@ class P2PClient {
174
224
  const data = await resp.json();
175
225
  return data.session;
176
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
+ }
177
266
  async capabilitySnapshot() {
178
267
  try {
179
268
  const resp = await fetch(`${this.baseUrl}/capabilities/snapshot`, { headers: this.authHeaders() });
@@ -356,6 +445,10 @@ class P2PClient {
356
445
  s2sReady: data.s2sReady ?? false,
357
446
  sttProvider: data.sttProvider ?? undefined,
358
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,
359
452
  };
360
453
  }
361
454
  /**
@@ -670,6 +763,186 @@ class P2PClient {
670
763
  }
671
764
  }
672
765
  /** Internal helper for authenticated GET/POST requests. */
766
+ /**
767
+ * Convergence point for ALL feedback surfaces — Tasks tab, in-Yaver
768
+ * native pane, and this standalone SDK all POST the same shape to
769
+ * `/tasks`. Wraps the user's text with the shared prompt builder
770
+ * (see `_core/buildFeedbackPrompt`) so every surface conditions
771
+ * the AI the same way.
772
+ *
773
+ * Returns the agent's response payload (`taskId`, etc.) so callers
774
+ * can wire `streamTaskOutput()` next for live transcript.
775
+ *
776
+ * Inputs:
777
+ * - userPrompt what the user typed
778
+ * - projectName / path optional Hot-Reload project context
779
+ * - runner / model optional preferred coding agent + model
780
+ * - screenshotBase64 optional JPEG base64 (no `data:` prefix)
781
+ * - imageMimeType defaults to "image/jpeg"
782
+ */
783
+ async createFeedbackTask(input) {
784
+ const { buildFeedbackPrompt } = await Promise.resolve().then(() => __importStar(require('./_core/buildFeedbackPrompt')));
785
+ const hasScreenshot = !!(input.screenshotBase64 && input.screenshotBase64.length > 0);
786
+ const description = buildFeedbackPrompt({
787
+ userPrompt: input.userPrompt,
788
+ projectName: input.projectName,
789
+ projectPath: input.projectPath,
790
+ hasScreenshot,
791
+ });
792
+ const images = [];
793
+ if (hasScreenshot && input.screenshotBase64) {
794
+ images.push({
795
+ base64: input.screenshotBase64,
796
+ mimeType: input.imageMimeType ?? 'image/jpeg',
797
+ filename: `yaver-feedback-${Math.floor(Date.now() / 1000)}.jpg`,
798
+ });
799
+ }
800
+ const body = {
801
+ title: input.userPrompt.slice(0, 80),
802
+ description,
803
+ userPrompt: input.userPrompt,
804
+ source: 'mobile-feedback',
805
+ images,
806
+ };
807
+ if (input.projectPath && input.projectPath.trim())
808
+ body.workDir = input.projectPath.trim();
809
+ if (input.projectName && input.projectName.trim())
810
+ body.projectName = input.projectName.trim();
811
+ if (input.runner && input.runner.trim())
812
+ body.runner = input.runner.trim();
813
+ if (input.model && input.model.trim())
814
+ body.model = input.model.trim();
815
+ const resp = await fetch(`${this.baseUrl}/tasks`, {
816
+ method: 'POST',
817
+ headers: this.authHeaders({ 'Content-Type': 'application/json' }),
818
+ body: JSON.stringify(body),
819
+ });
820
+ if (!resp.ok) {
821
+ const text = await resp.text().catch(() => '');
822
+ throw new Error(`createFeedbackTask HTTP ${resp.status}: ${text}`);
823
+ }
824
+ const json = (await resp.json().catch(() => ({})));
825
+ if (!json.taskId) {
826
+ throw new Error('createFeedbackTask: agent did not return taskId');
827
+ }
828
+ return { taskId: json.taskId, raw: json };
829
+ }
830
+ /**
831
+ * Subscribe to a task's live stdout/stderr stream. Returns an abort
832
+ * function — call it to detach. The agent emits NDJSON lines on
833
+ * `/tasks/{id}/output`; we surface each line via `onLine`.
834
+ *
835
+ * `onComplete` fires when the agent reports the task entered a
836
+ * terminal status (completed / failed / stopped). After that the
837
+ * caller should stop calling abort().
838
+ *
839
+ * Robust to fetch streaming on Hermes (streams Body via Response.
840
+ * body.getReader on platforms that support it; falls back to
841
+ * polling `/tasks/{id}` every 750 ms if streaming isn't available).
842
+ */
843
+ streamTaskOutput(taskId, onLine, onComplete) {
844
+ const ctrl = new AbortController();
845
+ let closed = false;
846
+ const close = () => {
847
+ if (closed)
848
+ return;
849
+ closed = true;
850
+ try {
851
+ ctrl.abort();
852
+ }
853
+ catch { /* ignore */ }
854
+ };
855
+ (async () => {
856
+ try {
857
+ const resp = await fetch(`${this.baseUrl}/tasks/${encodeURIComponent(taskId)}/output`, {
858
+ method: 'GET',
859
+ headers: this.authHeaders({ Accept: 'text/event-stream' }),
860
+ signal: ctrl.signal,
861
+ });
862
+ if (!resp.ok) {
863
+ throw new Error(`streamTaskOutput HTTP ${resp.status}`);
864
+ }
865
+ // RN Hermes: Response.body may be undefined. Fall back to
866
+ // polling final state.
867
+ const body = resp.body;
868
+ if (!body || typeof body.getReader !== 'function') {
869
+ await pollTaskUntilDone(this, taskId, onLine, onComplete, () => closed);
870
+ return;
871
+ }
872
+ const reader = body.getReader();
873
+ const decoder = new TextDecoder();
874
+ let buf = '';
875
+ while (!closed) {
876
+ const { value, done } = await reader.read();
877
+ if (done)
878
+ break;
879
+ buf += decoder.decode(value, { stream: true });
880
+ // SSE frames are separated by \n\n; payloads are `data: <json>\n`.
881
+ let idx = buf.indexOf('\n\n');
882
+ while (idx >= 0) {
883
+ const frame = buf.slice(0, idx);
884
+ buf = buf.slice(idx + 2);
885
+ for (const line of frame.split('\n')) {
886
+ if (line.startsWith('data:')) {
887
+ const payload = line.slice(5).trim();
888
+ if (payload)
889
+ onLine(payload);
890
+ }
891
+ }
892
+ idx = buf.indexOf('\n\n');
893
+ }
894
+ }
895
+ // Stream closed cleanly — query final status.
896
+ try {
897
+ const final = await fetch(`${this.baseUrl}/tasks/${encodeURIComponent(taskId)}`, { headers: this.authHeaders() });
898
+ const j = (await final.json().catch(() => ({})));
899
+ onComplete(j.status ?? 'completed');
900
+ }
901
+ catch {
902
+ onComplete('completed');
903
+ }
904
+ }
905
+ catch (e) {
906
+ if (!closed) {
907
+ // Surface the error via onLine so the UI shows it inline,
908
+ // then mark complete so the caller stops waiting.
909
+ onLine(`__error__: ${e instanceof Error ? e.message : String(e)}`);
910
+ onComplete('failed');
911
+ }
912
+ }
913
+ })();
914
+ return close;
915
+ }
916
+ /**
917
+ * Send a follow-up message into an existing task — multi-turn vibe
918
+ * chat. The agent's `/tasks/{id}/resume` accepts the same shape as
919
+ * `/tasks` (description / userPrompt / images), and the existing
920
+ * task picks back up with the same runner + project context.
921
+ */
922
+ async resumeTask(input) {
923
+ const images = [];
924
+ if (input.screenshotBase64 && input.screenshotBase64.length > 0) {
925
+ images.push({
926
+ base64: input.screenshotBase64,
927
+ mimeType: input.imageMimeType ?? 'image/jpeg',
928
+ filename: `yaver-feedback-followup-${Math.floor(Date.now() / 1000)}.jpg`,
929
+ });
930
+ }
931
+ const resp = await fetch(`${this.baseUrl}/tasks/${encodeURIComponent(input.taskId)}/resume`, {
932
+ method: 'POST',
933
+ headers: this.authHeaders({ 'Content-Type': 'application/json' }),
934
+ body: JSON.stringify({
935
+ description: input.userPrompt,
936
+ userPrompt: input.userPrompt,
937
+ source: 'mobile-feedback',
938
+ images,
939
+ }),
940
+ });
941
+ if (!resp.ok) {
942
+ const text = await resp.text().catch(() => '');
943
+ throw new Error(`resumeTask HTTP ${resp.status}: ${text}`);
944
+ }
945
+ }
673
946
  async request(method, path) {
674
947
  const response = await fetch(`${this.baseUrl}${path}`, {
675
948
  method,
@@ -683,3 +956,43 @@ class P2PClient {
683
956
  }
684
957
  }
685
958
  exports.P2PClient = P2PClient;
959
+ /**
960
+ * Fallback path used by streamTaskOutput when the platform's fetch
961
+ * returns a Response with no streaming body (older Hermes builds).
962
+ * Polls `/tasks/{id}` every 750 ms; surfaces newly-appended output
963
+ * lines via `onLine`, fires `onComplete` when status is terminal.
964
+ */
965
+ async function pollTaskUntilDone(client, taskId, onLine, onComplete, isClosed) {
966
+ // Use bracket access to read the private baseUrl/authHeaders without
967
+ // making them public — confined to this file's scope.
968
+ const c = client;
969
+ let lastLen = 0;
970
+ for (;;) {
971
+ if (isClosed())
972
+ return;
973
+ try {
974
+ const r = await fetch(`${c.baseUrl}/tasks/${encodeURIComponent(taskId)}`, {
975
+ headers: c.authHeaders(),
976
+ });
977
+ const j = (await r.json().catch(() => ({})));
978
+ const all = Array.isArray(j.output) ? j.output : (j.output ? [j.output] : []);
979
+ const flat = all.join('\n');
980
+ if (flat.length > lastLen) {
981
+ const fresh = flat.slice(lastLen);
982
+ lastLen = flat.length;
983
+ for (const ln of fresh.split('\n')) {
984
+ if (ln.length > 0)
985
+ onLine(ln);
986
+ }
987
+ }
988
+ if (j.status && ['completed', 'failed', 'stopped'].includes(j.status)) {
989
+ onComplete(j.status);
990
+ return;
991
+ }
992
+ }
993
+ catch {
994
+ // Transient — keep polling.
995
+ }
996
+ await new Promise((res) => setTimeout(res, 750));
997
+ }
998
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import type { P2PClient } from './P2PClient';
3
+ export type VibeTurnRole = 'user' | 'assistant' | 'status';
4
+ export interface VibeTurn {
5
+ id: string;
6
+ role: VibeTurnRole;
7
+ text: string;
8
+ timestamp: number;
9
+ }
10
+ interface Props {
11
+ client: P2PClient;
12
+ initialTaskId: string;
13
+ initialUserPrompt: string;
14
+ onClose?: () => void;
15
+ /** Called when the user taps Reload after a task completes — uses
16
+ * P2PClient.reloadApp() with the active project context. */
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;
23
+ }
24
+ export declare function VibeChatScreen({ client, initialTaskId, initialUserPrompt, onClose, onReload, project, model, runner, }: Props): React.JSX.Element;
25
+ export {};