yaver-feedback-react-native 0.9.0 → 0.9.3
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 +102 -1
- package/app.plugin.js +126 -9
- package/dist/AuthOverlay.d.ts +1 -14
- package/dist/AuthOverlay.js +9 -62
- package/dist/BlackBox.js +9 -1
- package/dist/DeployPanel.js +65 -0
- package/dist/Discovery.js +13 -8
- package/dist/DogfoodRuntime.d.ts +124 -0
- package/dist/DogfoodRuntime.js +273 -0
- package/dist/FeedbackModal.js +505 -65
- package/dist/LoginScreen.d.ts +1 -5
- package/dist/LoginScreen.js +2 -6
- package/dist/MachinePickerScreen.d.ts +1 -3
- package/dist/MachinePickerScreen.js +6 -18
- package/dist/P2PClient.d.ts +178 -2
- package/dist/P2PClient.js +493 -3
- package/dist/P2PDogfoodDriver.d.ts +12 -0
- package/dist/P2PDogfoodDriver.js +118 -0
- package/dist/PairDeviceModal.d.ts +2 -3
- package/dist/ShakeDetector.js +2 -0
- package/dist/VibeChatScreen.d.ts +11 -1
- package/dist/VibeChatScreen.js +200 -41
- package/dist/YaverFeedback.d.ts +132 -0
- package/dist/YaverFeedback.js +633 -65
- package/dist/YaverModeBadge.d.ts +22 -0
- package/dist/YaverModeBadge.js +219 -0
- package/dist/__tests__/AuthDevices.test.js +1 -48
- package/dist/__tests__/BlackBox.relayPassword.test.d.ts +1 -0
- package/dist/__tests__/BlackBox.relayPassword.test.js +105 -0
- package/dist/__tests__/BlackBoxAutoStart.test.d.ts +1 -0
- package/dist/__tests__/BlackBoxAutoStart.test.js +91 -0
- package/dist/__tests__/BlackBoxAutoStartColdStart.test.d.ts +1 -0
- package/dist/__tests__/BlackBoxAutoStartColdStart.test.js +156 -0
- package/dist/__tests__/BrowserLaneIcon.test.d.ts +3 -0
- package/dist/__tests__/BrowserLaneIcon.test.js +80 -0
- package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
- package/dist/__tests__/DogfoodRuntime.test.js +116 -0
- package/dist/__tests__/P2PClient.test.js +2 -2
- package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
- package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
- package/dist/__tests__/ReportIdentity.test.d.ts +25 -0
- package/dist/__tests__/ReportIdentity.test.js +180 -0
- package/dist/__tests__/SDKToken.test.js +1 -1
- package/dist/__tests__/ShakeToggle.test.d.ts +1 -0
- package/dist/__tests__/ShakeToggle.test.js +121 -0
- package/dist/__tests__/YaverFeedback.test.js +13 -3
- package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
- package/dist/__tests__/deviceDogfood.test.js +86 -0
- package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
- package/dist/__tests__/dogfoodPolicy.test.js +33 -0
- package/dist/__tests__/pickTargetDevice.test.d.ts +1 -0
- package/dist/__tests__/pickTargetDevice.test.js +89 -0
- package/dist/__tests__/reloadActions.test.d.ts +1 -0
- package/dist/__tests__/reloadActions.test.js +129 -0
- package/dist/__tests__/reloadActionsParity.test.d.ts +1 -0
- package/dist/__tests__/reloadActionsParity.test.js +42 -0
- package/dist/__tests__/types.test.js +5 -5
- package/dist/_core/ansi.d.ts +117 -0
- package/dist/_core/ansi.js +468 -0
- package/dist/_core/ansi.test.d.ts +1 -0
- package/dist/_core/ansi.test.js +225 -0
- package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
- package/dist/_core/buildFeedbackPrompt.js +18 -72
- package/dist/_core/constants.d.ts +19 -6
- package/dist/_core/constants.js +20 -7
- package/dist/_core/device.d.ts +1 -5
- package/dist/_core/device.js +5 -14
- package/dist/_core/endpoints.d.ts +0 -7
- package/dist/_core/endpoints.js +0 -7
- package/dist/_core/index.d.ts +4 -0
- package/dist/_core/index.js +4 -0
- package/dist/_core/remoteless.d.ts +44 -0
- package/dist/_core/remoteless.js +75 -0
- package/dist/_core/trace.d.ts +47 -0
- package/dist/_core/trace.js +38 -0
- package/dist/_core/trace.test.d.ts +1 -0
- package/dist/_core/trace.test.js +60 -0
- package/dist/auth.d.ts +3 -60
- package/dist/auth.js +6 -88
- package/dist/deviceDogfood.d.ts +53 -0
- package/dist/deviceDogfood.js +137 -0
- package/dist/dogfoodPolicy.d.ts +25 -0
- package/dist/dogfoodPolicy.js +24 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +33 -7
- package/dist/reloadActions.d.ts +88 -0
- package/dist/reloadActions.js +200 -0
- package/dist/storeShots.d.ts +67 -0
- package/dist/storeShots.js +137 -0
- package/dist/types.d.ts +208 -8
- package/package.json +13 -4
- package/src/AuthOverlay.tsx +20 -106
- package/src/BlackBox.ts +10 -1
- package/src/DeployPanel.tsx +74 -0
- package/src/Discovery.ts +13 -8
- package/src/DogfoodRuntime.ts +373 -0
- package/src/FeedbackModal.tsx +619 -79
- package/src/LoginScreen.tsx +2 -22
- package/src/MachinePickerScreen.tsx +6 -21
- package/src/P2PClient.ts +580 -4
- package/src/P2PDogfoodDriver.ts +132 -0
- package/src/PairDeviceModal.tsx +2 -3
- package/src/ShakeDetector.ts +1 -0
- package/src/VibeChatScreen.tsx +232 -42
- package/src/YaverFeedback.ts +631 -68
- package/src/YaverModeBadge.tsx +234 -0
- package/src/__tests__/AuthDevices.test.ts +1 -52
- package/src/__tests__/BlackBox.relayPassword.test.ts +129 -0
- package/src/__tests__/BlackBoxAutoStart.test.ts +111 -0
- package/src/__tests__/BlackBoxAutoStartColdStart.test.ts +191 -0
- package/src/__tests__/BrowserLaneIcon.test.ts +85 -0
- package/src/__tests__/DogfoodRuntime.test.ts +135 -0
- package/src/__tests__/P2PClient.test.ts +2 -2
- package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
- package/src/__tests__/ReportIdentity.test.ts +212 -0
- package/src/__tests__/SDKToken.test.ts +1 -1
- package/src/__tests__/ShakeToggle.test.ts +153 -0
- package/src/__tests__/YaverFeedback.test.ts +15 -3
- package/src/__tests__/deviceDogfood.test.ts +77 -0
- package/src/__tests__/dogfoodPolicy.test.ts +34 -0
- package/src/__tests__/pickTargetDevice.test.ts +101 -0
- package/src/__tests__/reloadActions.test.ts +171 -0
- package/src/__tests__/reloadActionsParity.test.ts +49 -0
- package/src/__tests__/types.test.ts +5 -5
- package/src/_core/ansi.test.ts +250 -0
- package/src/_core/ansi.ts +475 -0
- package/src/_core/buildFeedbackPrompt.ts +18 -95
- package/src/_core/constants.ts +20 -6
- package/src/_core/device.ts +5 -16
- package/src/_core/endpoints.ts +0 -7
- package/src/_core/index.ts +4 -0
- package/src/_core/remoteless.ts +110 -0
- package/src/_core/trace.test.ts +58 -0
- package/src/_core/trace.ts +75 -0
- package/src/auth.ts +7 -156
- package/src/deviceDogfood.ts +171 -0
- package/src/dogfoodPolicy.ts +40 -0
- package/src/index.ts +61 -11
- package/src/reloadActions.ts +273 -0
- package/src/storeShots.ts +189 -0
- package/src/types.ts +209 -8
- package/dist/GuestOnboardingScreen.d.ts +0 -8
- package/dist/GuestOnboardingScreen.js +0 -282
- package/src/GuestOnboardingScreen.tsx +0 -307
package/dist/P2PClient.js
CHANGED
|
@@ -35,7 +35,27 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.P2PClient = void 0;
|
|
37
37
|
exports.resolveAppIdentity = resolveAppIdentity;
|
|
38
|
+
exports.resolveReportIdentity = resolveReportIdentity;
|
|
38
39
|
const react_native_1 = require("react-native");
|
|
40
|
+
const endpoints_1 = require("./_core/endpoints");
|
|
41
|
+
const reloadActions_1 = require("./reloadActions");
|
|
42
|
+
function unrefTimer(timer) {
|
|
43
|
+
const maybeNodeTimer = timer;
|
|
44
|
+
if (typeof maybeNodeTimer.unref === 'function') {
|
|
45
|
+
maybeNodeTimer.unref();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function dogfoodFetch(url, init, timeoutMs) {
|
|
49
|
+
const ctrl = new AbortController();
|
|
50
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
51
|
+
unrefTimer(timer);
|
|
52
|
+
try {
|
|
53
|
+
return await fetch(url, { ...init, signal: ctrl.signal });
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
39
59
|
/**
|
|
40
60
|
* Try to resolve `{projectName, bundleId}` for the running app so the
|
|
41
61
|
* agent can map the reload request to a MobileProject in its scan
|
|
@@ -84,6 +104,140 @@ function resolveAppIdentity(opts) {
|
|
|
84
104
|
out.projectPath = projectPath;
|
|
85
105
|
return out;
|
|
86
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Is this JS running inside Yaver's own container as a pushed Hermes guest?
|
|
109
|
+
*
|
|
110
|
+
* The YaverInfo native module is registered only by Yaver's app, so its
|
|
111
|
+
* presence means the ambient runtime describes Yaver rather than the app
|
|
112
|
+
* whose code is executing. Mirrors the checks in YaverFeedback.ts,
|
|
113
|
+
* ShakeDetector.ts and QuickActionIcon.tsx.
|
|
114
|
+
*/
|
|
115
|
+
function isInsideYaverContainer() {
|
|
116
|
+
try {
|
|
117
|
+
const { NativeModules } = require('react-native');
|
|
118
|
+
return !!NativeModules?.YaverInfo;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Read the guest project Yaver's host shell pinned for this bundle.
|
|
126
|
+
*
|
|
127
|
+
* When an app's JS is pushed into Yaver's container as a Hermes guest, the
|
|
128
|
+
* ambient runtime describes the HOST — `expo-constants` and `SettingsManager`
|
|
129
|
+
* both answer `Yaver` / `io.yaver.mobile`. Auto-resolution would therefore
|
|
130
|
+
* file every guest report against Yaver's own repo, and the fix task would
|
|
131
|
+
* edit the SDK instead of the app under test.
|
|
132
|
+
*
|
|
133
|
+
* Yaver already knows the right answer: picking a project in the Hot Reload
|
|
134
|
+
* tab calls `YaverInfo.setInheritedGuestProject(name, path)`. Only the name
|
|
135
|
+
* is used here — the agent resolves the path itself and deliberately ignores
|
|
136
|
+
* client-supplied ones on feedback reports.
|
|
137
|
+
*/
|
|
138
|
+
function resolveInheritedGuestProjectName() {
|
|
139
|
+
try {
|
|
140
|
+
const { NativeModules } = require('react-native');
|
|
141
|
+
const name = String(NativeModules?.YaverInfo?.inheritedGuestProjectName || '').trim();
|
|
142
|
+
return name || undefined;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Not inside Yaver's container, or react-native unavailable (unit tests).
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build the app-identity half of a feedback report's metadata.
|
|
151
|
+
*
|
|
152
|
+
* `resolveAppIdentity()` has always fed /vibing/execute and /dev/reload-app,
|
|
153
|
+
* so the agent could route a "vibe on THIS app" request to the right repo —
|
|
154
|
+
* but nothing fed /feedback. Reports arrived with no identity at all, so the
|
|
155
|
+
* agent's fix router fell through to its own working directory and edited
|
|
156
|
+
* whichever repo it happened to be sitting in. This closes that gap by
|
|
157
|
+
* reusing the same resolver for the feedback path.
|
|
158
|
+
*
|
|
159
|
+
* Precedence, most to least trustworthy:
|
|
160
|
+
* 1. What the host app declared (`FeedbackConfig.projectName`/`bundleId`).
|
|
161
|
+
* An app knows its own identity; nothing should override it.
|
|
162
|
+
* 2. The guest project Yaver's host shell pinned, when running as a Hermes
|
|
163
|
+
* guest. Ambient lookups describe Yaver there, not the guest.
|
|
164
|
+
* 3. Ambient expo-constants / native modules — correct for a standalone
|
|
165
|
+
* build, which is the common case.
|
|
166
|
+
*
|
|
167
|
+
* Every lookup is best-effort: a bare RN app with no expo-constants still
|
|
168
|
+
* produces a report, just one the agent resolves by its own means.
|
|
169
|
+
*/
|
|
170
|
+
function resolveReportIdentity(opts) {
|
|
171
|
+
let projectName = (opts?.projectName || '').trim() || undefined;
|
|
172
|
+
let bundleId = (opts?.bundleId || '').trim() || undefined;
|
|
173
|
+
let version;
|
|
174
|
+
let buildNumber;
|
|
175
|
+
if (isInsideYaverContainer()) {
|
|
176
|
+
// Every ambient lookup answers for the HOST here. Because the agent
|
|
177
|
+
// routes on bundle id first, letting Yaver's id through would resolve
|
|
178
|
+
// straight to Yaver's own repo and the guest's name would never be
|
|
179
|
+
// consulted — so nothing ambient is trusted in the container. The
|
|
180
|
+
// version is Yaver's too, and reporting it would only mislabel which
|
|
181
|
+
// build the report came from.
|
|
182
|
+
projectName = projectName || resolveInheritedGuestProjectName();
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const ambient = resolveAppIdentity({ projectName, bundleId });
|
|
186
|
+
projectName = ambient.projectName;
|
|
187
|
+
bundleId = ambient.bundleId;
|
|
188
|
+
try {
|
|
189
|
+
const Constants = require('expo-constants').default ?? require('expo-constants');
|
|
190
|
+
const cfg = Constants?.expoConfig ?? Constants?.manifest ?? {};
|
|
191
|
+
version = Constants?.nativeAppVersion || cfg?.version;
|
|
192
|
+
buildNumber =
|
|
193
|
+
Constants?.nativeBuildVersion ||
|
|
194
|
+
cfg?.ios?.buildNumber ||
|
|
195
|
+
(cfg?.android?.versionCode != null ? String(cfg.android.versionCode) : undefined);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// expo-constants not installed (bare RN). Version is cosmetic — the
|
|
199
|
+
// router keys off appName/bundleId, both of which survive without it.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const app = {};
|
|
203
|
+
if (bundleId)
|
|
204
|
+
app.bundleId = bundleId;
|
|
205
|
+
if (version)
|
|
206
|
+
app.version = version;
|
|
207
|
+
if (buildNumber)
|
|
208
|
+
app.buildNumber = buildNumber;
|
|
209
|
+
if (!projectName && !bundleId) {
|
|
210
|
+
return { app };
|
|
211
|
+
}
|
|
212
|
+
const project = { surface: opts?.surface ?? 'mobile' };
|
|
213
|
+
if (projectName) {
|
|
214
|
+
project.projectName = projectName;
|
|
215
|
+
project.appName = projectName;
|
|
216
|
+
}
|
|
217
|
+
// Note: projectPath is intentionally omitted — the agent ignores
|
|
218
|
+
// client-supplied paths on feedback reports and resolves them itself.
|
|
219
|
+
if (bundleId)
|
|
220
|
+
project.bundleId = bundleId;
|
|
221
|
+
if (opts?.surfaces)
|
|
222
|
+
project.surfaces = opts.surfaces;
|
|
223
|
+
if (opts?.stack)
|
|
224
|
+
project.stack = opts.stack;
|
|
225
|
+
if (opts?.stacks)
|
|
226
|
+
project.stacks = opts.stacks;
|
|
227
|
+
if (opts?.testSurfaces)
|
|
228
|
+
project.testSurfaces = opts.testSurfaces;
|
|
229
|
+
if (opts?.feedbackSdk)
|
|
230
|
+
project.feedbackSdk = opts.feedbackSdk;
|
|
231
|
+
if (opts?.feedbackTransport)
|
|
232
|
+
project.feedbackTransport = opts.feedbackTransport;
|
|
233
|
+
if (opts?.voiceCapabilities)
|
|
234
|
+
project.voiceCapabilities = opts.voiceCapabilities;
|
|
235
|
+
if (opts?.sttProvider)
|
|
236
|
+
project.sttProvider = opts.sttProvider;
|
|
237
|
+
if (opts?.ttsProvider)
|
|
238
|
+
project.ttsProvider = opts.ttsProvider;
|
|
239
|
+
return { appName: projectName, app, project };
|
|
240
|
+
}
|
|
87
241
|
/**
|
|
88
242
|
* Translate a raw Go-agent error into something a user can act on.
|
|
89
243
|
*
|
|
@@ -235,6 +389,29 @@ class P2PClient {
|
|
|
235
389
|
const data = await resp.json().catch(() => ({}));
|
|
236
390
|
return Array.isArray(data.runners) ? data.runners : [];
|
|
237
391
|
}
|
|
392
|
+
/** Canonical runner + model catalogue used by Vibing routing controls. */
|
|
393
|
+
async getAvailableRunners() {
|
|
394
|
+
const resp = await fetch(`${this.baseUrl}/agent/runners`, {
|
|
395
|
+
headers: this.authHeaders(),
|
|
396
|
+
});
|
|
397
|
+
if (!resp.ok) {
|
|
398
|
+
const text = await resp.text().catch(() => '');
|
|
399
|
+
throw new Error(`getAvailableRunners HTTP ${resp.status}: ${text}`);
|
|
400
|
+
}
|
|
401
|
+
const data = await resp.json().catch(() => ({}));
|
|
402
|
+
return Array.isArray(data.runners) ? data.runners : [];
|
|
403
|
+
}
|
|
404
|
+
/** Discovered runnable projects on the selected owner machine. Paths stay
|
|
405
|
+
* on that machine/transport; they are never registered in Yaver's backend. */
|
|
406
|
+
async listDogfoodProjects() {
|
|
407
|
+
const resp = await fetch(`${this.baseUrl}/projects`, { headers: this.authHeaders() });
|
|
408
|
+
if (!resp.ok) {
|
|
409
|
+
const body = await resp.text().catch(() => '');
|
|
410
|
+
throw new Error(`listDogfoodProjects HTTP ${resp.status}: ${body}`);
|
|
411
|
+
}
|
|
412
|
+
const data = await resp.json().catch(() => ({}));
|
|
413
|
+
return Array.isArray(data.projects) ? data.projects : [];
|
|
414
|
+
}
|
|
238
415
|
async getOpenCodeConfig() {
|
|
239
416
|
const resp = await fetch(`${this.baseUrl}/runner/opencode/config`, {
|
|
240
417
|
headers: this.authHeaders(),
|
|
@@ -327,19 +504,24 @@ class P2PClient {
|
|
|
327
504
|
}
|
|
328
505
|
/** Health check — returns true if the agent is reachable. */
|
|
329
506
|
async health() {
|
|
507
|
+
let timeoutId = null;
|
|
330
508
|
try {
|
|
331
509
|
const controller = new AbortController();
|
|
332
|
-
|
|
510
|
+
timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
511
|
+
unrefTimer(timeoutId);
|
|
333
512
|
const response = await fetch(`${this.baseUrl}/health`, {
|
|
334
513
|
method: 'GET',
|
|
335
514
|
signal: controller.signal,
|
|
336
515
|
});
|
|
337
|
-
clearTimeout(timeoutId);
|
|
338
516
|
return response.ok;
|
|
339
517
|
}
|
|
340
518
|
catch {
|
|
341
519
|
return false;
|
|
342
520
|
}
|
|
521
|
+
finally {
|
|
522
|
+
if (timeoutId)
|
|
523
|
+
clearTimeout(timeoutId);
|
|
524
|
+
}
|
|
343
525
|
}
|
|
344
526
|
/** Get agent info (hostname, version, platform). */
|
|
345
527
|
async info() {
|
|
@@ -488,6 +670,272 @@ class P2PClient {
|
|
|
488
670
|
* via the BlackBox command channel.
|
|
489
671
|
* @param mode - "dev" for hot reload, "bundle" for native bundle rebuild
|
|
490
672
|
*/
|
|
673
|
+
/**
|
|
674
|
+
* Read the dev server's state so the overlay can decide WHICH reload
|
|
675
|
+
* actions to offer, and disable the rest with a reason.
|
|
676
|
+
*
|
|
677
|
+
* Returns null when the machine cannot be reached at all — which the
|
|
678
|
+
* caller must render as "not connected", never as "no dev server".
|
|
679
|
+
* Those are two different problems with two different fixes.
|
|
680
|
+
*/
|
|
681
|
+
async getDevServerStatus() {
|
|
682
|
+
try {
|
|
683
|
+
const resp = await fetch(`${this.baseUrl}${endpoints_1.AGENT_ENDPOINTS.devStatus}`, {
|
|
684
|
+
headers: this.authHeaders(),
|
|
685
|
+
});
|
|
686
|
+
if (!resp.ok)
|
|
687
|
+
return null;
|
|
688
|
+
const data = await resp.json().catch(() => ({}));
|
|
689
|
+
return {
|
|
690
|
+
running: data.running === true,
|
|
691
|
+
building: data.building === true,
|
|
692
|
+
framework: typeof data.framework === 'string' ? data.framework : undefined,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Start the ordinary Projects runtime for an embedded Dogfood host.
|
|
701
|
+
* Requires a full signed-in-user token because /dev/start can spawn tools;
|
|
702
|
+
* a narrow feedback SDK token intentionally cannot use it.
|
|
703
|
+
*/
|
|
704
|
+
async startDogfoodDevServer(input) {
|
|
705
|
+
if (input.lane === 'hermes') {
|
|
706
|
+
const ack = await this.reloadApp('bundle', { projectPath: input.workDir });
|
|
707
|
+
return { running: ack.ok, framework: input.framework, workDir: input.workDir };
|
|
708
|
+
}
|
|
709
|
+
const ctrl = new AbortController();
|
|
710
|
+
const timer = setTimeout(() => ctrl.abort(), 45000);
|
|
711
|
+
unrefTimer(timer);
|
|
712
|
+
try {
|
|
713
|
+
const response = await fetch(`${this.baseUrl}/dev/start`, {
|
|
714
|
+
method: 'POST',
|
|
715
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
716
|
+
body: JSON.stringify({
|
|
717
|
+
framework: input.framework,
|
|
718
|
+
workDir: input.workDir,
|
|
719
|
+
platform: 'web',
|
|
720
|
+
caller: 'sdk',
|
|
721
|
+
}),
|
|
722
|
+
signal: ctrl.signal,
|
|
723
|
+
});
|
|
724
|
+
const data = (await response.json().catch(() => ({})));
|
|
725
|
+
if (!response.ok) {
|
|
726
|
+
const error = new Error(data.error || `Dogfood preview start failed with HTTP ${response.status}`);
|
|
727
|
+
error.code = data.code || `DOGFOOD_DEV_START_HTTP_${response.status}`;
|
|
728
|
+
error.remedy = data.remedy || 'Fix the named dev-server failure, then retry Dogfood.';
|
|
729
|
+
error.retryable = data.retryable !== false;
|
|
730
|
+
error.capabilityGap = data.capabilityGap;
|
|
731
|
+
throw error;
|
|
732
|
+
}
|
|
733
|
+
return data;
|
|
734
|
+
}
|
|
735
|
+
finally {
|
|
736
|
+
clearTimeout(timer);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/** Full status for Dogfood startup; unlike the compact feedback snapshot,
|
|
740
|
+
* this retains render URLs and structured startup failures. */
|
|
741
|
+
async getDogfoodDevServerStatus() {
|
|
742
|
+
const ctrl = new AbortController();
|
|
743
|
+
const timer = setTimeout(() => ctrl.abort(), 10000);
|
|
744
|
+
unrefTimer(timer);
|
|
745
|
+
try {
|
|
746
|
+
const response = await fetch(`${this.baseUrl}${endpoints_1.AGENT_ENDPOINTS.devStatus}`, {
|
|
747
|
+
headers: this.authHeaders(),
|
|
748
|
+
signal: ctrl.signal,
|
|
749
|
+
});
|
|
750
|
+
if (!response.ok)
|
|
751
|
+
return null;
|
|
752
|
+
return await response.json().catch(() => null);
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
finally {
|
|
758
|
+
clearTimeout(timer);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
async getDogfoodRemoteRuntimeCapabilities(workDir, framework) {
|
|
762
|
+
const query = `?workDir=${encodeURIComponent(workDir)}&framework=${encodeURIComponent(framework)}`;
|
|
763
|
+
const response = await dogfoodFetch(`${this.baseUrl}/remote-runtime/capabilities${query}`, {
|
|
764
|
+
headers: this.authHeaders(),
|
|
765
|
+
}, 20000);
|
|
766
|
+
const data = await response.json().catch(() => ({}));
|
|
767
|
+
if (!response.ok)
|
|
768
|
+
throw new Error(data?.error || `Remote-runtime capabilities failed with HTTP ${response.status}`);
|
|
769
|
+
return { ...data, targets: Array.isArray(data?.targets) ? data.targets : [] };
|
|
770
|
+
}
|
|
771
|
+
async startDogfoodRemoteRuntime(workDir, framework, targetId) {
|
|
772
|
+
const response = await dogfoodFetch(`${this.baseUrl}/remote-runtime/sessions`, {
|
|
773
|
+
method: 'POST',
|
|
774
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
775
|
+
body: JSON.stringify({ workDir, framework, targetId, surface: 'sdk' }),
|
|
776
|
+
}, 45000);
|
|
777
|
+
const data = await response.json().catch(() => ({}));
|
|
778
|
+
if (!response.ok)
|
|
779
|
+
throw new Error(data?.error || `Remote-runtime start failed with HTTP ${response.status}`);
|
|
780
|
+
return data;
|
|
781
|
+
}
|
|
782
|
+
async stopDogfoodRemoteRuntime(sessionId) {
|
|
783
|
+
const response = await dogfoodFetch(`${this.baseUrl}/remote-runtime/sessions/${encodeURIComponent(sessionId)}`, {
|
|
784
|
+
method: 'DELETE', headers: this.authHeaders(),
|
|
785
|
+
}, 15000);
|
|
786
|
+
if (!response.ok)
|
|
787
|
+
throw new Error(`Remote-runtime stop failed with HTTP ${response.status}`);
|
|
788
|
+
}
|
|
789
|
+
/** Stop the runtime this SDK host started. Full user auth, same as start. */
|
|
790
|
+
async stopDogfoodDevServer() {
|
|
791
|
+
const ctrl = new AbortController();
|
|
792
|
+
const timer = setTimeout(() => ctrl.abort(), 15000);
|
|
793
|
+
unrefTimer(timer);
|
|
794
|
+
try {
|
|
795
|
+
const response = await fetch(`${this.baseUrl}/dev/stop`, {
|
|
796
|
+
method: 'POST', headers: this.authHeaders(), signal: ctrl.signal,
|
|
797
|
+
});
|
|
798
|
+
if (!response.ok)
|
|
799
|
+
throw new Error(`Dogfood preview stop failed with HTTP ${response.status}`);
|
|
800
|
+
}
|
|
801
|
+
finally {
|
|
802
|
+
clearTimeout(timer);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/** Resolve an agent-reported /dev/ or /dev-web/ route without putting auth in the URL. */
|
|
806
|
+
resolveDogfoodUrl(path) {
|
|
807
|
+
return new URL(path, `${this.baseUrl.replace(/\/+$/, '')}/`).toString();
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* React-Native-safe /dev/events stream. XHR is intentional: Hermes fetch
|
|
811
|
+
* exposes no streaming response body. Reconnects are bounded and every
|
|
812
|
+
* reconnect/loss is surfaced to the host console.
|
|
813
|
+
*/
|
|
814
|
+
subscribeDogfoodDevEvents(onEvent, onHealth) {
|
|
815
|
+
let disposed = false;
|
|
816
|
+
let xhr = null;
|
|
817
|
+
let retryTimer = null;
|
|
818
|
+
let attempt = 0;
|
|
819
|
+
const open = () => {
|
|
820
|
+
if (disposed)
|
|
821
|
+
return;
|
|
822
|
+
const request = new XMLHttpRequest();
|
|
823
|
+
xhr = request;
|
|
824
|
+
let parsed = 0;
|
|
825
|
+
let carry = '';
|
|
826
|
+
const consume = () => {
|
|
827
|
+
const text = request.responseText || '';
|
|
828
|
+
if (text.length <= parsed)
|
|
829
|
+
return;
|
|
830
|
+
carry += text.slice(parsed);
|
|
831
|
+
parsed = text.length;
|
|
832
|
+
const normalized = carry.replace(/\r\n/g, '\n');
|
|
833
|
+
const frames = normalized.split('\n\n');
|
|
834
|
+
carry = frames.pop() || '';
|
|
835
|
+
for (const frame of frames) {
|
|
836
|
+
const data = frame.split('\n')
|
|
837
|
+
.filter((line) => line.startsWith('data:'))
|
|
838
|
+
.map((line) => line.slice(5).replace(/^ /, ''))
|
|
839
|
+
.join('\n');
|
|
840
|
+
if (!data)
|
|
841
|
+
continue;
|
|
842
|
+
try {
|
|
843
|
+
attempt = 0;
|
|
844
|
+
onHealth?.(null);
|
|
845
|
+
onEvent(JSON.parse(data));
|
|
846
|
+
}
|
|
847
|
+
catch { /* comments and non-JSON keepalives are valid SSE */ }
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
const reconnect = (reason) => {
|
|
851
|
+
if (disposed)
|
|
852
|
+
return;
|
|
853
|
+
if (attempt >= 5) {
|
|
854
|
+
onHealth?.({ kind: 'lost', message: `Dev-server logs stopped: ${reason}` });
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
const wait = Math.min(8000, 500 * (2 ** attempt));
|
|
858
|
+
attempt += 1;
|
|
859
|
+
onHealth?.({ kind: 'reattaching', message: `Dev-server logs interrupted; reconnecting (${attempt}/5): ${reason}` });
|
|
860
|
+
retryTimer = setTimeout(open, wait);
|
|
861
|
+
unrefTimer(retryTimer);
|
|
862
|
+
};
|
|
863
|
+
request.open('GET', `${this.baseUrl}/dev/events`, true);
|
|
864
|
+
for (const [key, value] of Object.entries(this.authHeaders({ Accept: 'text/event-stream' }))) {
|
|
865
|
+
try {
|
|
866
|
+
request.setRequestHeader(key, value);
|
|
867
|
+
}
|
|
868
|
+
catch { /* restricted RN header */ }
|
|
869
|
+
}
|
|
870
|
+
request.onprogress = consume;
|
|
871
|
+
request.onload = () => { consume(); reconnect(`HTTP ${request.status || 0}`); };
|
|
872
|
+
request.onerror = () => reconnect('connection failed');
|
|
873
|
+
request.onabort = () => reconnect('connection aborted');
|
|
874
|
+
request.ontimeout = () => reconnect('connection timed out');
|
|
875
|
+
try {
|
|
876
|
+
request.send();
|
|
877
|
+
}
|
|
878
|
+
catch (error) {
|
|
879
|
+
reconnect(error instanceof Error ? error.message : 'connection could not start');
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
open();
|
|
883
|
+
return () => {
|
|
884
|
+
disposed = true;
|
|
885
|
+
if (retryTimer)
|
|
886
|
+
clearTimeout(retryTimer);
|
|
887
|
+
try {
|
|
888
|
+
xhr?.abort();
|
|
889
|
+
}
|
|
890
|
+
catch { /* idempotent */ }
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Trigger a reload with an EXPLICIT fast/full mode — no bundle fallback.
|
|
895
|
+
*
|
|
896
|
+
* `reloadApp('dev')` silently falls through to a bundle rebuild when the
|
|
897
|
+
* dev server is down, which is right for a one-button UX and wrong the
|
|
898
|
+
* moment the user picks between two named actions: someone who pressed
|
|
899
|
+
* "Full Reload" must not get a 60-second bundle rebuild without being
|
|
900
|
+
* told. So this method reports the failure instead, with a named cause.
|
|
901
|
+
*
|
|
902
|
+
* Auth: the SAME bearer used for the feedback POST. `/dev/reload` is
|
|
903
|
+
* registered under `authSDK` in desktop/agent/httpserver.go and is already
|
|
904
|
+
* in the `reload` SDK-token scope list — nothing widens.
|
|
905
|
+
*/
|
|
906
|
+
async reloadWithMode(mode, snapshot) {
|
|
907
|
+
if (mode === 'bundle')
|
|
908
|
+
return this.reloadApp('bundle');
|
|
909
|
+
let resp;
|
|
910
|
+
try {
|
|
911
|
+
resp = await fetch(`${this.baseUrl}${reloadActions_1.RELOAD_PATH}`, {
|
|
912
|
+
method: 'POST',
|
|
913
|
+
headers: this.authHeaders({ 'Content-Type': 'application/json' }),
|
|
914
|
+
body: JSON.stringify({ mode }),
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
catch (err) {
|
|
918
|
+
throw new Error((0, reloadActions_1.describeReloadFailure)(0, err instanceof Error ? err.message : '', snapshot));
|
|
919
|
+
}
|
|
920
|
+
if (!resp.ok) {
|
|
921
|
+
const text = await resp.text().catch(() => '');
|
|
922
|
+
throw new Error((0, reloadActions_1.describeReloadFailure)(resp.status, text, snapshot));
|
|
923
|
+
}
|
|
924
|
+
const payload = await resp.json().catch(() => ({}));
|
|
925
|
+
const nativeChangesDetected = payload.nativeChangesDetected === true;
|
|
926
|
+
return {
|
|
927
|
+
ok: true,
|
|
928
|
+
mode: 'dev',
|
|
929
|
+
acknowledged: true,
|
|
930
|
+
nativeChangesDetected,
|
|
931
|
+
changeClass: typeof payload.changeClass === 'string' ? payload.changeClass : undefined,
|
|
932
|
+
message: nativeChangesDetected
|
|
933
|
+
? 'Reload accepted, but native files changed — a rebuild is required.'
|
|
934
|
+
: mode === 'full'
|
|
935
|
+
? 'Full reload requested.'
|
|
936
|
+
: 'Hot reload requested.',
|
|
937
|
+
};
|
|
938
|
+
}
|
|
491
939
|
async reloadApp(mode = 'bundle', opts) {
|
|
492
940
|
// Default path: always rebuild a fresh Hermes bundle.
|
|
493
941
|
//
|
|
@@ -565,7 +1013,7 @@ class P2PClient {
|
|
|
565
1013
|
* the project context plus the user's prompt. Returns the task id the
|
|
566
1014
|
* caller can poll via `/tasks/{id}` if needed.
|
|
567
1015
|
*
|
|
568
|
-
* Requires an owner/CLI
|
|
1016
|
+
* Requires an owner/CLI token — the `/vibing*` routes do not
|
|
569
1017
|
* currently accept SDK-minted tokens. Power users typically drive
|
|
570
1018
|
* vibing from Claude Code / the Yaver mobile app; this method is a
|
|
571
1019
|
* convenience for the SDK's one-tap bug-report-to-vibing path.
|
|
@@ -587,6 +1035,8 @@ class P2PClient {
|
|
|
587
1035
|
projectPath: identity.projectPath ?? opts?.projectPath ?? '',
|
|
588
1036
|
projectName: identity.projectName,
|
|
589
1037
|
bundleId: identity.bundleId,
|
|
1038
|
+
runner: opts?.runner,
|
|
1039
|
+
model: opts?.model,
|
|
590
1040
|
}),
|
|
591
1041
|
});
|
|
592
1042
|
if (!response.ok) {
|
|
@@ -827,6 +1277,46 @@ class P2PClient {
|
|
|
827
1277
|
}
|
|
828
1278
|
return { taskId: json.taskId, raw: json };
|
|
829
1279
|
}
|
|
1280
|
+
/** Existing task history, filtered by the agent to Vibing/feedback topics. */
|
|
1281
|
+
async listVibeThreads(input) {
|
|
1282
|
+
const params = new URLSearchParams();
|
|
1283
|
+
if (input?.projectName)
|
|
1284
|
+
params.set('projectName', input.projectName);
|
|
1285
|
+
if (input?.projectPath)
|
|
1286
|
+
params.set('projectPath', input.projectPath);
|
|
1287
|
+
const query = params.toString();
|
|
1288
|
+
const resp = await fetch(`${this.baseUrl}/vibing/tasks${query ? `?${query}` : ''}`, {
|
|
1289
|
+
headers: this.authHeaders(),
|
|
1290
|
+
});
|
|
1291
|
+
if (!resp.ok)
|
|
1292
|
+
throw new Error(`listVibeThreads HTTP ${resp.status}`);
|
|
1293
|
+
const json = (await resp.json().catch(() => ({})));
|
|
1294
|
+
return (json.tasks || []).map((task) => ({
|
|
1295
|
+
id: String(task.id),
|
|
1296
|
+
title: String(task.title || 'New topic'),
|
|
1297
|
+
status: String(task.status || 'completed'),
|
|
1298
|
+
createdAt: task.createdAt,
|
|
1299
|
+
projectName: task.projectName,
|
|
1300
|
+
turnCount: task.turnCount,
|
|
1301
|
+
}));
|
|
1302
|
+
}
|
|
1303
|
+
async getVibeThread(taskId) {
|
|
1304
|
+
const resp = await fetch(`${this.baseUrl}/vibing/task/${encodeURIComponent(taskId)}`, {
|
|
1305
|
+
headers: this.authHeaders(),
|
|
1306
|
+
});
|
|
1307
|
+
if (!resp.ok)
|
|
1308
|
+
throw new Error(`getVibeThread HTTP ${resp.status}`);
|
|
1309
|
+
const json = (await resp.json().catch(() => ({})));
|
|
1310
|
+
return json.task || json;
|
|
1311
|
+
}
|
|
1312
|
+
async deleteVibeThread(taskId) {
|
|
1313
|
+
const resp = await fetch(`${this.baseUrl}/vibing/task/${encodeURIComponent(taskId)}`, {
|
|
1314
|
+
method: 'DELETE',
|
|
1315
|
+
headers: this.authHeaders(),
|
|
1316
|
+
});
|
|
1317
|
+
if (!resp.ok)
|
|
1318
|
+
throw new Error(`deleteVibeThread HTTP ${resp.status}`);
|
|
1319
|
+
}
|
|
830
1320
|
/**
|
|
831
1321
|
* Subscribe to a task's live stdout/stderr stream. Returns an abort
|
|
832
1322
|
* function — call it to detach. The agent emits NDJSON lines on
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { P2PClient } from './P2PClient';
|
|
2
|
+
import { type DogfoodDriver } from './DogfoodRuntime';
|
|
3
|
+
export interface P2PDogfoodDriverOptions {
|
|
4
|
+
startupTimeoutMs?: number;
|
|
5
|
+
pollIntervalMs?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Ready-to-use driver for app owners who authenticate with Yaver's normal
|
|
9
|
+
* account flow. Their UI owns the trigger and preview surface; this adapter
|
|
10
|
+
* owns the existing Projects endpoints and raw build log stream.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createP2PDogfoodDriver(client: P2PClient, options?: P2PDogfoodDriverOptions): DogfoodDriver;
|