yaver-feedback-react-native 0.9.2 → 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.
Files changed (103) hide show
  1. package/README.md +102 -1
  2. package/dist/AuthOverlay.d.ts +1 -14
  3. package/dist/AuthOverlay.js +9 -62
  4. package/dist/Discovery.js +0 -6
  5. package/dist/DogfoodRuntime.d.ts +124 -0
  6. package/dist/DogfoodRuntime.js +273 -0
  7. package/dist/FeedbackModal.js +347 -61
  8. package/dist/LoginScreen.d.ts +1 -5
  9. package/dist/LoginScreen.js +2 -6
  10. package/dist/MachinePickerScreen.d.ts +1 -3
  11. package/dist/MachinePickerScreen.js +6 -18
  12. package/dist/P2PClient.d.ts +116 -3
  13. package/dist/P2PClient.js +273 -3
  14. package/dist/P2PDogfoodDriver.d.ts +12 -0
  15. package/dist/P2PDogfoodDriver.js +118 -0
  16. package/dist/PairDeviceModal.d.ts +2 -3
  17. package/dist/VibeChatScreen.d.ts +11 -1
  18. package/dist/VibeChatScreen.js +200 -41
  19. package/dist/YaverFeedback.d.ts +34 -0
  20. package/dist/YaverFeedback.js +124 -19
  21. package/dist/YaverModeBadge.d.ts +22 -0
  22. package/dist/YaverModeBadge.js +219 -0
  23. package/dist/__tests__/AuthDevices.test.js +1 -48
  24. package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
  25. package/dist/__tests__/DogfoodRuntime.test.js +116 -0
  26. package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
  27. package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
  28. package/dist/__tests__/ReportIdentity.test.d.ts +25 -1
  29. package/dist/__tests__/ReportIdentity.test.js +34 -22
  30. package/dist/__tests__/YaverFeedback.test.js +13 -3
  31. package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
  32. package/dist/__tests__/deviceDogfood.test.js +86 -0
  33. package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
  34. package/dist/__tests__/dogfoodPolicy.test.js +33 -0
  35. package/dist/_core/ansi.d.ts +117 -0
  36. package/dist/_core/ansi.js +468 -0
  37. package/dist/_core/ansi.test.d.ts +1 -0
  38. package/dist/_core/ansi.test.js +225 -0
  39. package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
  40. package/dist/_core/buildFeedbackPrompt.js +18 -72
  41. package/dist/_core/constants.d.ts +19 -6
  42. package/dist/_core/constants.js +20 -7
  43. package/dist/_core/device.d.ts +9 -23
  44. package/dist/_core/device.js +13 -28
  45. package/dist/_core/endpoints.d.ts +0 -7
  46. package/dist/_core/endpoints.js +0 -7
  47. package/dist/_core/index.d.ts +4 -0
  48. package/dist/_core/index.js +4 -0
  49. package/dist/_core/remoteless.d.ts +44 -0
  50. package/dist/_core/remoteless.js +75 -0
  51. package/dist/_core/trace.d.ts +47 -0
  52. package/dist/_core/trace.js +38 -0
  53. package/dist/_core/trace.test.d.ts +1 -0
  54. package/dist/_core/trace.test.js +60 -0
  55. package/dist/auth.d.ts +3 -60
  56. package/dist/auth.js +6 -88
  57. package/dist/deviceDogfood.d.ts +53 -0
  58. package/dist/deviceDogfood.js +137 -0
  59. package/dist/dogfoodPolicy.d.ts +25 -0
  60. package/dist/dogfoodPolicy.js +24 -0
  61. package/dist/index.d.ts +13 -4
  62. package/dist/index.js +24 -8
  63. package/dist/reloadActions.js +2 -2
  64. package/dist/types.d.ts +50 -7
  65. package/package.json +12 -3
  66. package/src/AuthOverlay.tsx +20 -106
  67. package/src/Discovery.ts +0 -6
  68. package/src/DogfoodRuntime.ts +373 -0
  69. package/src/FeedbackModal.tsx +445 -67
  70. package/src/LoginScreen.tsx +2 -22
  71. package/src/MachinePickerScreen.tsx +6 -21
  72. package/src/P2PClient.ts +347 -4
  73. package/src/P2PDogfoodDriver.ts +132 -0
  74. package/src/PairDeviceModal.tsx +2 -3
  75. package/src/VibeChatScreen.tsx +232 -42
  76. package/src/YaverFeedback.ts +133 -22
  77. package/src/YaverModeBadge.tsx +234 -0
  78. package/src/__tests__/AuthDevices.test.ts +1 -52
  79. package/src/__tests__/DogfoodRuntime.test.ts +135 -0
  80. package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
  81. package/src/__tests__/ReportIdentity.test.ts +36 -27
  82. package/src/__tests__/YaverFeedback.test.ts +15 -3
  83. package/src/__tests__/deviceDogfood.test.ts +77 -0
  84. package/src/__tests__/dogfoodPolicy.test.ts +34 -0
  85. package/src/_core/ansi.test.ts +250 -0
  86. package/src/_core/ansi.ts +475 -0
  87. package/src/_core/buildFeedbackPrompt.ts +18 -95
  88. package/src/_core/constants.ts +20 -6
  89. package/src/_core/device.ts +13 -30
  90. package/src/_core/endpoints.ts +0 -7
  91. package/src/_core/index.ts +4 -0
  92. package/src/_core/remoteless.ts +110 -0
  93. package/src/_core/trace.test.ts +58 -0
  94. package/src/_core/trace.ts +75 -0
  95. package/src/auth.ts +7 -156
  96. package/src/deviceDogfood.ts +171 -0
  97. package/src/dogfoodPolicy.ts +40 -0
  98. package/src/index.ts +40 -11
  99. package/src/reloadActions.ts +2 -2
  100. package/src/types.ts +45 -7
  101. package/dist/GuestOnboardingScreen.d.ts +0 -8
  102. package/dist/GuestOnboardingScreen.js +0 -282
  103. package/src/GuestOnboardingScreen.tsx +0 -307
@@ -41,10 +41,6 @@ export interface CoreDevice {
41
41
  runnerDown: boolean;
42
42
  /** Unix ms of the latest heartbeat the agent sent to Convex. */
43
43
  lastHeartbeat: number;
44
- isGuest: boolean;
45
- hostName?: string;
46
- hostEmail?: string;
47
- accessScope?: 'owner' | 'shared-scoped' | 'shared-legacy';
48
44
  /** Primary LAN IP (or tunnel host) the agent advertised. */
49
45
  quicHost: string;
50
46
  quicPort: number;
@@ -75,10 +71,6 @@ function normHost(host: string | undefined): string {
75
71
  export function deviceIdentityKey(d: CoreDevice): string {
76
72
  if (d.hwid) return `hwid:${d.hwid}`;
77
73
  if (d.publicKey) return `pub:${d.publicKey}`;
78
- if (d.isGuest) {
79
- const scope = d.hostEmail || d.hostName || 'guest';
80
- return `guest:${scope}:${d.deviceId || d.name}`;
81
- }
82
74
  const n = normName(d.name);
83
75
  const os = String(d.platform || '').trim().toLowerCase();
84
76
  if (n && os) return `host:${os}:${n}`;
@@ -87,7 +79,6 @@ export function deviceIdentityKey(d: CoreDevice): string {
87
79
  }
88
80
 
89
81
  export function deviceAliasKey(d: CoreDevice): string | null {
90
- if (d.isGuest) return null;
91
82
  const n = normName(d.name);
92
83
  const os = String(d.platform || '').trim().toLowerCase();
93
84
  if (!n || !os) return null;
@@ -95,7 +86,6 @@ export function deviceAliasKey(d: CoreDevice): string | null {
95
86
  }
96
87
 
97
88
  export function deviceEndpointKey(d: CoreDevice): string | null {
98
- if (d.isGuest) return null;
99
89
  const h = normHost(d.quicHost);
100
90
  if (!h) return null;
101
91
  return `${h}:${d.quicPort || 0}`;
@@ -212,11 +202,10 @@ export function collapseDevices(devices: CoreDevice[]): CoreDevice[] {
212
202
  // ── Freshness + target pick ───────────────────────────────────────────
213
203
 
214
204
  /**
215
- * "Fresh" matches the mobile app: online + heartbeat within
216
- * HEARTBEAT_STALE_MS. Clients read Convex's `isOnline` first (the backend
217
- * applies the same gate from the server clock), then use this helper when
218
- * they need the phone-side freshness opinion too — e.g. for auto-connect
219
- * picks.
205
+ * "Fresh" matches the mobile app: online + heartbeat < 90 s. Clients
206
+ * read Convex's `isOnline` first (backend already applies its own 90 s
207
+ * gate from the server clock), then use this helper when they need the
208
+ * phone-side freshness opinion too — e.g. for auto-connect picks.
220
209
  */
221
210
  export function isDeviceFresh(d: CoreDevice, now = Date.now()): boolean {
222
211
  if (!d.isOnline) return false;
@@ -225,20 +214,11 @@ export function isDeviceFresh(d: CoreDevice, now = Date.now()): boolean {
225
214
  }
226
215
 
227
216
  /**
228
- * Choose the best candidate for an auto-connect attempt.
229
- *
230
- * An explicit `preferredDeviceId` is honoured by id ALONE, or not at all:
231
- * - A missing `quicHost` is not grounds to reroute. Relay transport
232
- * addresses a device by id (`<relay>/d/<deviceId>`), so the entry is
233
- * still reachable off-LAN — which is precisely when quicHost is absent.
234
- * - If the id is not in the list, return null. Falling through to another
235
- * machine silently lands the user's fix on the wrong host: they pick the
236
- * Mac mini, the commit shows up on the laptop, and nothing reports an
237
- * error. Not connecting is the better failure — the caller surfaces
238
- * "selected machine missing, re-select it".
239
- *
240
- * With no preference, fall back: fresh + quicHost → online + quicHost →
241
- * first with a quicHost.
217
+ * Choose the best candidate for an auto-connect attempt. Preference:
218
+ * 1. explicit `preferredDeviceId`, by identity alone; missing means fail
219
+ * 2. fresh (online + recent heartbeat) + has a quicHost
220
+ * 3. online + has a quicHost
221
+ * 4. first with a quicHost
242
222
  */
243
223
  export function pickTargetDevice(
244
224
  devices: CoreDevice[],
@@ -246,7 +226,10 @@ export function pickTargetDevice(
246
226
  ): CoreDevice | null {
247
227
  if (!devices.length) return null;
248
228
  if (preferredDeviceId) {
249
- return devices.find((d) => d.deviceId === preferredDeviceId) ?? null;
229
+ // An off-LAN device normally has no quicHost; the relay addresses it by
230
+ // id. Never fall through to a different healthy machine after the user
231
+ // selected one explicitly — failure is safer than misrouting their work.
232
+ return devices.find((d) => d.deviceId === preferredDeviceId) || null;
250
233
  }
251
234
  const fresh = devices.find((d) => isDeviceFresh(d) && d.quicHost);
252
235
  if (fresh) return fresh;
@@ -65,13 +65,6 @@ export const CONVEX_ENDPOINTS = {
65
65
  authLogin: '/auth/login',
66
66
  userSettings: '/settings',
67
67
  platformConfig: '/config',
68
- guestsList: '/guests/list',
69
- guestsHosts: '/guests/hosts',
70
- guestsAllowed: '/guests/allowed',
71
- guestsInvite: '/guests/invite',
72
- guestsAccept: '/guests/accept',
73
- guestsAcceptCode: '/guests/accept-code',
74
- guestsRevoke: '/guests/revoke',
75
68
  } as const;
76
69
 
77
70
  /** Routes on a relay server. */
@@ -18,3 +18,7 @@
18
18
  export * from './constants';
19
19
  export * from './endpoints';
20
20
  export * from './device';
21
+ export * from './ansi';
22
+ export * from './trace';
23
+ export * from './remoteless';
24
+ export * from './buildFeedbackPrompt';
@@ -0,0 +1,110 @@
1
+ // AUTO-SYNCED from shared/client-core/src/remoteless.ts.
2
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
3
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
4
+
5
+ // One pure decision for every client surface that can explicitly select, or
6
+ // fall back to, a local or hosted DeepSeek lane. Remoteless is bounded capacity
7
+ // and never becomes the default ahead of configured devices and runners.
8
+
9
+ export type RemotelessCapability =
10
+ | "analysis-chat" | "code-edit" | "git-read" | "git-commit" | "git-push"
11
+ | "static-preflight" | "existing-web-artifact"
12
+ | "dev-server" | "web-build" | "flutter-render" | "native-build"
13
+ | "simulator" | "shell" | "test" | "deploy" | "container";
14
+
15
+ export type RemotelessSupport = "supported" | "bounded" | "unavailable";
16
+
17
+ export type RemotelessCapabilityResult = {
18
+ capability: RemotelessCapability;
19
+ support: RemotelessSupport;
20
+ code: string;
21
+ summary: string;
22
+ detail: string;
23
+ route: { label: string; path: "/devices" | "/cloud-onboarding" };
24
+ alternateRoute?: { label: string; path: "/devices" | "/cloud-onboarding" };
25
+ };
26
+
27
+ export type ExecutionCandidate = {
28
+ id: string;
29
+ name: string;
30
+ role: "explicit" | "primary" | "secondary" | "focused";
31
+ connected: boolean;
32
+ };
33
+
34
+ export type RemotelessPlacement =
35
+ | { lane: "remote"; target: ExecutionCandidate; degraded: boolean; banner: string | null }
36
+ | { lane: "remoteless"; capability: RemotelessCapabilityResult; banner: string }
37
+ | { lane: "blocked"; capability: RemotelessCapabilityResult; banner: string };
38
+
39
+ const LOCAL_SUPPORT: Record<RemotelessCapability, RemotelessSupport> = {
40
+ "analysis-chat": "supported", "code-edit": "bounded", "git-read": "supported", "git-commit": "bounded",
41
+ "git-push": "bounded", "static-preflight": "supported",
42
+ "existing-web-artifact": "supported", "dev-server": "unavailable",
43
+ "web-build": "unavailable", "flutter-render": "unavailable",
44
+ "native-build": "unavailable", simulator: "unavailable", shell: "unavailable",
45
+ test: "unavailable", deploy: "unavailable", container: "unavailable",
46
+ };
47
+
48
+ const LABELS: Record<RemotelessCapability, string> = {
49
+ "analysis-chat": "analysis and chat", "code-edit": "editing", "git-read": "Git inspection", "git-commit": "Git commit",
50
+ "git-push": "Git push", "static-preflight": "static preflight",
51
+ "existing-web-artifact": "an existing web artifact", "dev-server": "a dev server",
52
+ "web-build": "a web build", "flutter-render": "Flutter rendering",
53
+ "native-build": "a native build", simulator: "a simulator", shell: "shell commands",
54
+ test: "tests", deploy: "deployment", container: "containers",
55
+ };
56
+
57
+ export function remotelessCapability(capability: RemotelessCapability, surface: "ios" | "android" | "web" | "companion"): RemotelessCapabilityResult {
58
+ const support = capability === "analysis-chat" ? "supported" : surface === "ios" || surface === "android"
59
+ ? LOCAL_SUPPORT[capability]
60
+ : capability === "existing-web-artifact" ? "supported" : "unavailable";
61
+ const label = LABELS[capability];
62
+ const host = surface === "ios" ? "iPhone/iPad" : surface === "android" ? "Android device" : surface === "web" ? "browser" : "companion device";
63
+ if (support === "supported") return {
64
+ capability, support, code: `remoteless.${capability}.supported`,
65
+ summary: `${label} can run on this device`,
66
+ detail: `${label} does not require a remote shell or build toolchain.`,
67
+ route: { label: "Choose a device", path: "/devices" },
68
+ };
69
+ if (support === "bounded") return {
70
+ capability, support, code: `remoteless.${capability}.bounded`,
71
+ summary: `${label} is available on this device with limits`,
72
+ detail: `${label} can run locally, but background execution is bounded and interrupted work returns to Review.`,
73
+ route: { label: "Choose a device", path: "/devices" },
74
+ };
75
+ return {
76
+ capability, support, code: `remoteless.${capability}.unavailable`,
77
+ summary: `${label} needs another execution target`,
78
+ detail: capability === "flutter-render"
79
+ ? `The ${host} can display an already-built Flutter web artifact, but Yaver's remoteless runtime has no Flutter SDK, shell, dev server, or simulator. Build and serve it on your primary/secondary device or Cloud Workspace.`
80
+ : `Yaver's remoteless runtime on this ${host} cannot provide ${label}: it has no general shell, package manager, persistent process host, simulator, native SDK, or container runtime. Use an eligible primary/secondary device or Cloud Workspace.`,
81
+ route: { label: "Choose a capable device", path: "/devices" },
82
+ alternateRoute: { label: "Use Cloud Workspace", path: "/cloud-onboarding" },
83
+ };
84
+ }
85
+
86
+ export function resolveRemotelessPlacement(input: {
87
+ capability: RemotelessCapability;
88
+ surface: "ios" | "android" | "web" | "companion";
89
+ candidates: ExecutionCandidate[];
90
+ forceLocal?: boolean;
91
+ }): RemotelessPlacement {
92
+ const capability = remotelessCapability(input.capability, input.surface);
93
+ if (!input.forceLocal) {
94
+ // Enforce precedence here instead of trusting every surface to construct
95
+ // its array correctly. Within a role, caller order remains significant
96
+ // (for example project-assigned primary before account-wide primary).
97
+ const target = (["explicit", "primary", "secondary", "focused"] as const)
98
+ .map((role) => input.candidates.find((candidate) => candidate.role === role && candidate.connected))
99
+ .find((candidate): candidate is ExecutionCandidate => !!candidate);
100
+ if (target) {
101
+ const degraded = target.role === "secondary";
102
+ return { lane: "remote", target, degraded, banner: degraded ? `Using secondary · ${target.name} · primary unavailable` : null };
103
+ }
104
+ }
105
+ const configured = input.candidates.filter((candidate) => candidate.role !== "focused");
106
+ const reason = configured.length ? "Primary and secondary devices are unavailable." : "No eligible remote device is configured.";
107
+ if (capability.support === "unavailable") return { lane: "blocked", capability, banner: `${reason} ${capability.summary}.` };
108
+ if (input.forceLocal) return { lane: "remoteless", capability, banner: `No remote box selected · ${capability.summary}.` };
109
+ return { lane: "remoteless", capability, banner: `Remoteless fallback · ${reason} ${capability.summary}.` };
110
+ }
@@ -0,0 +1,58 @@
1
+ // AUTO-SYNCED from shared/client-core/src/trace.test.ts.
2
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
3
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
4
+
5
+ /**
6
+ * trace.test.ts — guards for the shared trace assembler.
7
+ *
8
+ * Run: npx tsx shared/client-core/src/trace.test.ts
9
+ */
10
+ import { assembleTrace } from "./trace";
11
+
12
+ let failures = 0;
13
+ const eq = (got: unknown, want: unknown, label: string) => {
14
+ if (JSON.stringify(got) === JSON.stringify(want)) console.log(`ok ${label}`);
15
+ else { console.error(`FAIL ${label}:\n got ${JSON.stringify(got)}\n want ${JSON.stringify(want)}`); failures++; }
16
+ };
17
+ const ok = (c: unknown, label: string) => eq(Boolean(c), true, label);
18
+
19
+ {
20
+ const t = assembleTrace({
21
+ surface: "web",
22
+ surfaceVersion: "1.1.164",
23
+ agentVersion: "1.99.409",
24
+ device: "ubuntu-4gb-hel1-1 (2ed7da41…)",
25
+ relay: "public-free",
26
+ task: { id: "abc123", status: "failed", runner: "opencode", model: "deepseek/deepseek-v4-flash", title: "build" },
27
+ error: "flutter exited before becoming ready",
28
+ raw: "the raw failure bytes",
29
+ logTail: "line1\nline2",
30
+ ts: 1700000000000,
31
+ });
32
+ ok(t.includes("surface: web"), "surface line present");
33
+ ok(t.includes("agent.version: 1.99.409"), "agent version present");
34
+ ok(t.includes("task: abc123 status=failed runner=opencode"), "task line present");
35
+ ok(t.includes("error: flutter exited before becoming ready"), "error present");
36
+ ok(t.includes("log-tail:\nline1\nline2"), "log tail present under its label");
37
+ ok(t.includes("ts: 1700000000000"), "timestamp present");
38
+ }
39
+ {
40
+ // No invented fields when absent.
41
+ const t = assembleTrace({ surface: "mobile", task: { id: "x" } });
42
+ ok(!t.includes("agent.version"), "no agent.version when absent");
43
+ ok(!t.includes("error:"), "no error when absent");
44
+ ok(t.includes("surface: mobile"), "surface still present");
45
+ }
46
+ {
47
+ // Secrets are redacted.
48
+ const t = assembleTrace({ surface: "web", error: "failed with token=abc123xyz" });
49
+ ok(t.includes("[redacted]") && !t.includes("abc123xyz"), "token redacted from error");
50
+ const t2 = assembleTrace({ surface: "mobile", raw: "Authorization: Bearer deadbeef" });
51
+ ok(t2.includes("[redacted]") && !t2.includes("deadbeef"), "bearer redacted from raw");
52
+ }
53
+
54
+ if (failures > 0) {
55
+ console.error(`\n${failures} FAILURE(s)`);
56
+ process.exit(1);
57
+ }
58
+ console.log("\nall trace tests pass");
@@ -0,0 +1,75 @@
1
+ // AUTO-SYNCED from shared/client-core/src/trace.ts.
2
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
3
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
4
+
5
+ /**
6
+ * trace.ts — one shared paste-ready trace assembler for every surface.
7
+ *
8
+ * WHY (2026-08-09): a crash/failure on web, mobile, or the console used to
9
+ * copy different things — RawFailureBanner copied just `failure.raw`, the
10
+ * mobile Logs sheet copied `combinedLogText`, RuntimeLab copied raw console
11
+ * lines. None carried the surface identity, versions, task id, runner/model,
12
+ * or the log tail, so pasting into an issue or a vibing follow-up lost the
13
+ * context that makes a bug reproducible. One assembler here gives every
14
+ * surface the SAME structured blob (surface → versions → device/relay →
15
+ * task → error → log tail), so web and mobile can never drift
16
+ * (AGENTS.md: one shared classifier, no copies). Mirrored into
17
+ * mobile/src/_core and web/lib/_core by scripts/sync-client-core.sh.
18
+ */
19
+
20
+ export interface TraceContext {
21
+ /** Surface name: "web" | "mobile" | "console" | "cli" | "watch" ... */
22
+ surface: string;
23
+ /** e.g. web build label, mobile JS bundle version, cli version. */
24
+ surfaceVersion?: string;
25
+ /** Agent version reported by /info (e.g. "1.99.409"). */
26
+ agentVersion?: string;
27
+ /** Device the task ran on (name + id). */
28
+ device?: string;
29
+ /** Relay region / id if known. */
30
+ relay?: string;
31
+ /** Task identity + lifecycle. */
32
+ task?: {
33
+ id: string;
34
+ status?: string;
35
+ runner?: string;
36
+ model?: string;
37
+ title?: string;
38
+ };
39
+ /** The human-readable error (already extracted/named). */
40
+ error?: string;
41
+ /** Raw failure blob (the undecorated original). */
42
+ raw?: string;
43
+ /** Log tail — last N lines of the relevant log (agent/relay/runtime). */
44
+ logTail?: string;
45
+ /** When the trace was captured (ms epoch). */
46
+ ts?: number;
47
+ }
48
+
49
+ const MASK = /token\s*[=:]\s*\S+|Bearer\s+\S+|api[_-]?key\s*[=:]\s*\S+|password\s*[=:]\s*\S+/gi;
50
+
51
+ function sanitize(s: string): string {
52
+ return String(s || "").replace(MASK, "[redacted]");
53
+ }
54
+
55
+ /**
56
+ * Assemble a paste-ready trace. Every present field becomes one labelled
57
+ * line; nothing is ever invented. Returns a plain-text blob.
58
+ */
59
+ export function assembleTrace(ctx: TraceContext): string {
60
+ const out: string[] = [];
61
+ out.push("--- Yaver trace ---");
62
+ out.push(`surface: ${sanitize(ctx.surface)}`);
63
+ if (ctx.surfaceVersion) out.push(`surface.version: ${sanitize(ctx.surfaceVersion)}`);
64
+ if (ctx.agentVersion) out.push(`agent.version: ${sanitize(ctx.agentVersion)}`);
65
+ if (ctx.device) out.push(`device: ${sanitize(ctx.device)}`);
66
+ if (ctx.relay) out.push(`relay: ${sanitize(ctx.relay)}`);
67
+ if (ctx.task) {
68
+ out.push(`task: ${sanitize(ctx.task.id)}${ctx.task.status ? ` status=${sanitize(ctx.task.status)}` : ""}${ctx.task.runner ? ` runner=${sanitize(ctx.task.runner)}` : ""}${ctx.task.model ? ` model=${sanitize(ctx.task.model)}` : ""}${ctx.task.title ? ` title=${sanitize(ctx.task.title)}` : ""}`);
69
+ }
70
+ if (ctx.error) out.push(`error: ${sanitize(ctx.error)}`);
71
+ if (ctx.raw) out.push(`raw: ${sanitize(ctx.raw)}`);
72
+ if (ctx.logTail) out.push(`log-tail:\n${sanitize(ctx.logTail)}`);
73
+ out.push(`ts: ${ctx.ts ?? Date.now()}`);
74
+ return out.join("\n");
75
+ }
package/src/auth.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * (`yaver://oauth-callback`) the Yaver mobile app uses.
11
11
  * - Email / password sign-up + login (no 2FA flow — for SDK simplicity).
12
12
  * - Token validation.
13
- * - `/devices/list` → owned + shared (guest) remote dev machines.
13
+ * - `/devices/list` → owner remote dev machines.
14
14
  *
15
15
  * Mobile-only. A web equivalent will ship as a separate `yaver-web-feedback`
16
16
  * package; do not import this module from a browser bundle.
@@ -399,7 +399,7 @@ export async function loginWithEmail(
399
399
  return { token: data.token, userId: data.userId };
400
400
  }
401
401
 
402
- // ─── Devices (owned + shared) ─────────────────────────────────────────
402
+ // ─── Owner devices ────────────────────────────────────────────────────
403
403
 
404
404
  export interface RemoteDevice {
405
405
  deviceId: string;
@@ -409,12 +409,6 @@ export interface RemoteDevice {
409
409
  needsAuth: boolean;
410
410
  runnerDown: boolean;
411
411
  lastHeartbeat: number;
412
- isGuest: boolean;
413
- hostUserId?: string;
414
- hostName?: string;
415
- hostEmail?: string;
416
- hostUserIdString?: string;
417
- accessScope: 'owner' | 'shared-scoped' | 'shared-legacy';
418
412
  quicHost: string;
419
413
  quicPort: number;
420
414
  /** Agent HTTP port — preferred over quicPort when present. */
@@ -432,7 +426,6 @@ export interface RemoteDevice {
432
426
 
433
427
  export interface DeviceList {
434
428
  owned: RemoteDevice[];
435
- shared: RemoteDevice[];
436
429
  }
437
430
 
438
431
  export interface DeviceReachability {
@@ -440,52 +433,9 @@ export interface DeviceReachability {
440
433
  url?: string;
441
434
  }
442
435
 
443
- export interface GuestInvitation {
444
- hostUserId: string;
445
- hostName: string;
446
- hostEmail: string;
447
- hostUserIdString?: string;
448
- createdAt: number;
449
- expiresAt: number;
450
- inviteCode?: string;
451
- }
452
-
453
- export interface ActiveGuestHost {
454
- hostUserId: string;
455
- hostName: string;
456
- hostEmail: string;
457
- grantedAt: number;
458
- }
459
-
460
- export interface GuestHostsResponse {
461
- pending: GuestInvitation[];
462
- active: ActiveGuestHost[];
463
- }
464
-
465
- export interface InvitationHostDevice {
466
- deviceId: string;
467
- name: string;
468
- platform: string;
469
- lastHeartbeat?: number;
470
- proposed: boolean;
471
- }
472
-
473
- export interface InvitationPreview {
474
- inviteCode: string;
475
- hostUserId: string;
476
- hostName: string;
477
- hostEmail: string;
478
- hostUserIdString?: string;
479
- proposedDeviceIds?: string[];
480
- hostDevices: InvitationHostDevice[];
481
- invitedByUserId?: boolean;
482
- expiresAt: number;
483
- createdAt: number;
484
- }
485
-
486
436
  /**
487
- * Fetch the set of remote dev machines this user can reach. Splits into
488
- * owned (user is the host) vs shared (host invited them as a guest).
437
+ * Fetch the signed-in owner's remote dev machines. Legacy non-owner rows are
438
+ * discarded even if a stale backend still returns them.
489
439
  *
490
440
  * Collapses duplicate rows before splitting — Convex can return multiple
491
441
  * rows per physical machine after a re-pair or hostname change, and the
@@ -498,7 +448,7 @@ export async function listReachableDevices(
498
448
  const res = await fetch(`${convexSiteUrl}/devices/list`, {
499
449
  headers: { Authorization: `Bearer ${token}` },
500
450
  });
501
- if (!res.ok) return { owned: [], shared: [] };
451
+ if (!res.ok) return { owned: [] };
502
452
  const data = await res.json();
503
453
  const raw = (data.devices ?? []) as any[];
504
454
  // Normalise Convex field names → SDK's RemoteDevice shape. The
@@ -512,12 +462,6 @@ export async function listReachableDevices(
512
462
  needsAuth: !!d.needsAuth,
513
463
  runnerDown: !!d.runnerDown,
514
464
  lastHeartbeat: d.lastHeartbeat ?? 0,
515
- isGuest: !!d.isGuest,
516
- hostUserId: d.hostUserId,
517
- hostName: d.hostName,
518
- hostEmail: d.hostEmail,
519
- hostUserIdString: d.hostUserIdString,
520
- accessScope: d.accessScope ?? 'owner',
521
465
  quicHost: d.quicHost ?? d.host ?? '',
522
466
  quicPort: d.quicPort ?? 0,
523
467
  httpPort: d.httpPort ?? d.quicPort,
@@ -532,12 +476,9 @@ export async function listReachableDevices(
532
476
  // Lazy require so Jest + tree-shakers don't choke on a circular import.
533
477
  const { collapseRemoteDevices } = require('./deviceDedup') as typeof import('./deviceDedup');
534
478
  const deduped = collapseRemoteDevices(normalised);
535
- return {
536
- owned: deduped.filter((d) => !d.isGuest),
537
- shared: deduped.filter((d) => d.isGuest),
538
- };
479
+ return { owned: deduped };
539
480
  } catch {
540
- return { owned: [], shared: [] };
481
+ return { owned: [] };
541
482
  }
542
483
  }
543
484
 
@@ -581,93 +522,3 @@ export async function probeDeviceReachability(
581
522
  }
582
523
  return { reachable: false };
583
524
  }
584
-
585
- export async function mintGuestSdkToken(
586
- token: string,
587
- hostUserId: string,
588
- targetDeviceId: string,
589
- ): Promise<{ token: string; expiresAt: number; allowedProjects?: string[] }> {
590
- const res = await fetch(`${convexSiteUrl}/guests/sdk-token`, {
591
- method: 'POST',
592
- headers: {
593
- Authorization: `Bearer ${token}`,
594
- 'Content-Type': 'application/json',
595
- },
596
- body: JSON.stringify({ hostUserId, targetDeviceId }),
597
- });
598
- if (!res.ok) {
599
- const data = await res.json().catch(() => ({}));
600
- throw new Error(data.error || 'Failed to mint delegated SDK token');
601
- }
602
- return res.json();
603
- }
604
-
605
- export async function fetchGuestHosts(token: string): Promise<GuestHostsResponse> {
606
- const res = await fetch(`${convexSiteUrl}/guests/hosts`, {
607
- headers: { Authorization: `Bearer ${token}` },
608
- });
609
- if (!res.ok) {
610
- const data = await res.json().catch(() => ({}));
611
- throw new Error(data.error || 'Failed to fetch guest hosts');
612
- }
613
- return res.json();
614
- }
615
-
616
- export async function findInviteByCode(
617
- token: string,
618
- code: string,
619
- ): Promise<InvitationPreview | null> {
620
- const cleaned = code.toUpperCase().trim();
621
- const res = await fetch(
622
- `${convexSiteUrl}/guests/find-by-code?code=${encodeURIComponent(cleaned)}`,
623
- { headers: { Authorization: `Bearer ${token}` } },
624
- );
625
- if (res.status === 404) return null;
626
- if (!res.ok) {
627
- const data = await res.json().catch(() => ({}));
628
- throw new Error(data.error || 'Failed to load invite');
629
- }
630
- return res.json();
631
- }
632
-
633
- export async function acceptGuestByCode(
634
- token: string,
635
- code: string,
636
- approvedDeviceIds?: string[],
637
- ): Promise<{ hostName: string; hostEmail: string }> {
638
- const res = await fetch(`${convexSiteUrl}/guests/accept-code`, {
639
- method: 'POST',
640
- headers: {
641
- Authorization: `Bearer ${token}`,
642
- 'Content-Type': 'application/json',
643
- },
644
- body: JSON.stringify({
645
- code: code.toUpperCase().trim(),
646
- approvedDeviceIds,
647
- }),
648
- });
649
- if (!res.ok) {
650
- const data = await res.json().catch(() => ({}));
651
- throw new Error(data.error || 'Invalid invite code');
652
- }
653
- return res.json();
654
- }
655
-
656
- export async function acceptGuestInvitation(
657
- token: string,
658
- hostUserId: string,
659
- approvedDeviceIds?: string[],
660
- ): Promise<void> {
661
- const res = await fetch(`${convexSiteUrl}/guests/accept`, {
662
- method: 'POST',
663
- headers: {
664
- Authorization: `Bearer ${token}`,
665
- 'Content-Type': 'application/json',
666
- },
667
- body: JSON.stringify({ hostUserId, approvedDeviceIds }),
668
- });
669
- if (!res.ok) {
670
- const data = await res.json().catch(() => ({}));
671
- throw new Error(data.error || 'Failed to accept invitation');
672
- }
673
- }