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.
Files changed (144) hide show
  1. package/README.md +102 -1
  2. package/app.plugin.js +126 -9
  3. package/dist/AuthOverlay.d.ts +1 -14
  4. package/dist/AuthOverlay.js +9 -62
  5. package/dist/BlackBox.js +9 -1
  6. package/dist/DeployPanel.js +65 -0
  7. package/dist/Discovery.js +13 -8
  8. package/dist/DogfoodRuntime.d.ts +124 -0
  9. package/dist/DogfoodRuntime.js +273 -0
  10. package/dist/FeedbackModal.js +505 -65
  11. package/dist/LoginScreen.d.ts +1 -5
  12. package/dist/LoginScreen.js +2 -6
  13. package/dist/MachinePickerScreen.d.ts +1 -3
  14. package/dist/MachinePickerScreen.js +6 -18
  15. package/dist/P2PClient.d.ts +178 -2
  16. package/dist/P2PClient.js +493 -3
  17. package/dist/P2PDogfoodDriver.d.ts +12 -0
  18. package/dist/P2PDogfoodDriver.js +118 -0
  19. package/dist/PairDeviceModal.d.ts +2 -3
  20. package/dist/ShakeDetector.js +2 -0
  21. package/dist/VibeChatScreen.d.ts +11 -1
  22. package/dist/VibeChatScreen.js +200 -41
  23. package/dist/YaverFeedback.d.ts +132 -0
  24. package/dist/YaverFeedback.js +633 -65
  25. package/dist/YaverModeBadge.d.ts +22 -0
  26. package/dist/YaverModeBadge.js +219 -0
  27. package/dist/__tests__/AuthDevices.test.js +1 -48
  28. package/dist/__tests__/BlackBox.relayPassword.test.d.ts +1 -0
  29. package/dist/__tests__/BlackBox.relayPassword.test.js +105 -0
  30. package/dist/__tests__/BlackBoxAutoStart.test.d.ts +1 -0
  31. package/dist/__tests__/BlackBoxAutoStart.test.js +91 -0
  32. package/dist/__tests__/BlackBoxAutoStartColdStart.test.d.ts +1 -0
  33. package/dist/__tests__/BlackBoxAutoStartColdStart.test.js +156 -0
  34. package/dist/__tests__/BrowserLaneIcon.test.d.ts +3 -0
  35. package/dist/__tests__/BrowserLaneIcon.test.js +80 -0
  36. package/dist/__tests__/DogfoodRuntime.test.d.ts +1 -0
  37. package/dist/__tests__/DogfoodRuntime.test.js +116 -0
  38. package/dist/__tests__/P2PClient.test.js +2 -2
  39. package/dist/__tests__/P2PDogfoodDriver.test.d.ts +1 -0
  40. package/dist/__tests__/P2PDogfoodDriver.test.js +64 -0
  41. package/dist/__tests__/ReportIdentity.test.d.ts +25 -0
  42. package/dist/__tests__/ReportIdentity.test.js +180 -0
  43. package/dist/__tests__/SDKToken.test.js +1 -1
  44. package/dist/__tests__/ShakeToggle.test.d.ts +1 -0
  45. package/dist/__tests__/ShakeToggle.test.js +121 -0
  46. package/dist/__tests__/YaverFeedback.test.js +13 -3
  47. package/dist/__tests__/deviceDogfood.test.d.ts +1 -0
  48. package/dist/__tests__/deviceDogfood.test.js +86 -0
  49. package/dist/__tests__/dogfoodPolicy.test.d.ts +1 -0
  50. package/dist/__tests__/dogfoodPolicy.test.js +33 -0
  51. package/dist/__tests__/pickTargetDevice.test.d.ts +1 -0
  52. package/dist/__tests__/pickTargetDevice.test.js +89 -0
  53. package/dist/__tests__/reloadActions.test.d.ts +1 -0
  54. package/dist/__tests__/reloadActions.test.js +129 -0
  55. package/dist/__tests__/reloadActionsParity.test.d.ts +1 -0
  56. package/dist/__tests__/reloadActionsParity.test.js +42 -0
  57. package/dist/__tests__/types.test.js +5 -5
  58. package/dist/_core/ansi.d.ts +117 -0
  59. package/dist/_core/ansi.js +468 -0
  60. package/dist/_core/ansi.test.d.ts +1 -0
  61. package/dist/_core/ansi.test.js +225 -0
  62. package/dist/_core/buildFeedbackPrompt.d.ts +4 -9
  63. package/dist/_core/buildFeedbackPrompt.js +18 -72
  64. package/dist/_core/constants.d.ts +19 -6
  65. package/dist/_core/constants.js +20 -7
  66. package/dist/_core/device.d.ts +1 -5
  67. package/dist/_core/device.js +5 -14
  68. package/dist/_core/endpoints.d.ts +0 -7
  69. package/dist/_core/endpoints.js +0 -7
  70. package/dist/_core/index.d.ts +4 -0
  71. package/dist/_core/index.js +4 -0
  72. package/dist/_core/remoteless.d.ts +44 -0
  73. package/dist/_core/remoteless.js +75 -0
  74. package/dist/_core/trace.d.ts +47 -0
  75. package/dist/_core/trace.js +38 -0
  76. package/dist/_core/trace.test.d.ts +1 -0
  77. package/dist/_core/trace.test.js +60 -0
  78. package/dist/auth.d.ts +3 -60
  79. package/dist/auth.js +6 -88
  80. package/dist/deviceDogfood.d.ts +53 -0
  81. package/dist/deviceDogfood.js +137 -0
  82. package/dist/dogfoodPolicy.d.ts +25 -0
  83. package/dist/dogfoodPolicy.js +24 -0
  84. package/dist/index.d.ts +17 -4
  85. package/dist/index.js +33 -7
  86. package/dist/reloadActions.d.ts +88 -0
  87. package/dist/reloadActions.js +200 -0
  88. package/dist/storeShots.d.ts +67 -0
  89. package/dist/storeShots.js +137 -0
  90. package/dist/types.d.ts +208 -8
  91. package/package.json +13 -4
  92. package/src/AuthOverlay.tsx +20 -106
  93. package/src/BlackBox.ts +10 -1
  94. package/src/DeployPanel.tsx +74 -0
  95. package/src/Discovery.ts +13 -8
  96. package/src/DogfoodRuntime.ts +373 -0
  97. package/src/FeedbackModal.tsx +619 -79
  98. package/src/LoginScreen.tsx +2 -22
  99. package/src/MachinePickerScreen.tsx +6 -21
  100. package/src/P2PClient.ts +580 -4
  101. package/src/P2PDogfoodDriver.ts +132 -0
  102. package/src/PairDeviceModal.tsx +2 -3
  103. package/src/ShakeDetector.ts +1 -0
  104. package/src/VibeChatScreen.tsx +232 -42
  105. package/src/YaverFeedback.ts +631 -68
  106. package/src/YaverModeBadge.tsx +234 -0
  107. package/src/__tests__/AuthDevices.test.ts +1 -52
  108. package/src/__tests__/BlackBox.relayPassword.test.ts +129 -0
  109. package/src/__tests__/BlackBoxAutoStart.test.ts +111 -0
  110. package/src/__tests__/BlackBoxAutoStartColdStart.test.ts +191 -0
  111. package/src/__tests__/BrowserLaneIcon.test.ts +85 -0
  112. package/src/__tests__/DogfoodRuntime.test.ts +135 -0
  113. package/src/__tests__/P2PClient.test.ts +2 -2
  114. package/src/__tests__/P2PDogfoodDriver.test.ts +80 -0
  115. package/src/__tests__/ReportIdentity.test.ts +212 -0
  116. package/src/__tests__/SDKToken.test.ts +1 -1
  117. package/src/__tests__/ShakeToggle.test.ts +153 -0
  118. package/src/__tests__/YaverFeedback.test.ts +15 -3
  119. package/src/__tests__/deviceDogfood.test.ts +77 -0
  120. package/src/__tests__/dogfoodPolicy.test.ts +34 -0
  121. package/src/__tests__/pickTargetDevice.test.ts +101 -0
  122. package/src/__tests__/reloadActions.test.ts +171 -0
  123. package/src/__tests__/reloadActionsParity.test.ts +49 -0
  124. package/src/__tests__/types.test.ts +5 -5
  125. package/src/_core/ansi.test.ts +250 -0
  126. package/src/_core/ansi.ts +475 -0
  127. package/src/_core/buildFeedbackPrompt.ts +18 -95
  128. package/src/_core/constants.ts +20 -6
  129. package/src/_core/device.ts +5 -16
  130. package/src/_core/endpoints.ts +0 -7
  131. package/src/_core/index.ts +4 -0
  132. package/src/_core/remoteless.ts +110 -0
  133. package/src/_core/trace.test.ts +58 -0
  134. package/src/_core/trace.ts +75 -0
  135. package/src/auth.ts +7 -156
  136. package/src/deviceDogfood.ts +171 -0
  137. package/src/dogfoodPolicy.ts +40 -0
  138. package/src/index.ts +61 -11
  139. package/src/reloadActions.ts +273 -0
  140. package/src/storeShots.ts +189 -0
  141. package/src/types.ts +209 -8
  142. package/dist/GuestOnboardingScreen.d.ts +0 -8
  143. package/dist/GuestOnboardingScreen.js +0 -282
  144. package/src/GuestOnboardingScreen.tsx +0 -307
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ // AUTO-SYNCED from shared/client-core/src/ansi.test.ts.
3
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
4
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * ansi.test.ts — guards for the shared ANSI tokenizer.
8
+ *
9
+ * The bug this exists for (2026-08-09): the dashboard and mobile app both
10
+ * flattened opencode's raw ANSI stream to plain text with stripAnsi, losing
11
+ * every colour the console has, while the xterm Terminal view kept the
12
+ * bytes — two surfaces, two looks, one product. The tokenizer below is the
13
+ * single classifier both chat renderers consume; these tests pin the tokens
14
+ * so a renderer drift (web spans vs mobile nested Text) can never silently
15
+ * change what a user sees.
16
+ *
17
+ * Run: npx tsx shared/client-core/src/ansi.test.ts
18
+ */
19
+ const ansi_1 = require("./ansi");
20
+ let failures = 0;
21
+ const eq = (got, want, label) => {
22
+ if (JSON.stringify(got) === JSON.stringify(want))
23
+ console.log(`ok ${label}`);
24
+ else {
25
+ console.error(`FAIL ${label}:\n got ${JSON.stringify(got)}\n want ${JSON.stringify(want)}`);
26
+ failures++;
27
+ }
28
+ };
29
+ const ok = (c, label) => eq(Boolean(c), true, label);
30
+ // ── plain text passes through untouched ─────────────────────────────────
31
+ {
32
+ const t = (0, ansi_1.tokenizeAnsi)("hello world");
33
+ eq(t, [{ text: "hello world" }], "plain text → single unstyled token");
34
+ }
35
+ // ── SGR colours ──────────────────────────────────────────────────────────
36
+ {
37
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[31mred\x1b[0m plain");
38
+ eq(t, [
39
+ { text: "red", fg: { kind: "named", index: 1 } },
40
+ { text: " plain" },
41
+ ], "red text then reset → two tokens");
42
+ }
43
+ {
44
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[32m+\x1b[0m added");
45
+ eq(t, [
46
+ { text: "+", fg: { kind: "named", index: 2 } },
47
+ { text: " added" },
48
+ ], "green + line (git patch)");
49
+ }
50
+ {
51
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[1m\x1b[33m$ ls\x1b[0m");
52
+ eq(t, [
53
+ { text: "$ ls", fg: { kind: "named", index: 3 }, bold: true },
54
+ ], "bold yellow $ prompt");
55
+ }
56
+ // ── 256-colour palette ──────────────────────────────────────────────────
57
+ {
58
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[38;5;208morange\x1b[0m");
59
+ eq(t, [
60
+ { text: "orange", fg: { kind: "palette", index: 208 } },
61
+ ], "xterm-256 orange (208)");
62
+ }
63
+ {
64
+ const [r, g, b] = (0, ansi_1.paletteRgb)(208);
65
+ ok(r > 200 && g > 80 && g < 160 && b < 60, "palette 208 is orange-ish RGB");
66
+ }
67
+ {
68
+ const [r, g, b] = (0, ansi_1.paletteRgb)(2);
69
+ eq([r, g, b], [...ansi_1.ANSI_16_RGB[2]], "palette index <16 maps to the standard 16");
70
+ }
71
+ // ── truecolor ───────────────────────────────────────────────────────────
72
+ {
73
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[38;2;255;120;50mtrue\x1b[0m");
74
+ eq(t, [
75
+ { text: "true", fg: { kind: "rgb", rgb: [255, 120, 50] } },
76
+ ], "truecolor SGR 38;2;r;g;b");
77
+ }
78
+ // ── background ──────────────────────────────────────────────────────────
79
+ {
80
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[48;5;236mgray bg\x1b[0m");
81
+ eq(t, [
82
+ { text: "gray bg", bg: { kind: "palette", index: 236 } },
83
+ ], "xterm-256 background (patch gray)");
84
+ }
85
+ // ── bold / dim / underline / strike ─────────────────────────────────────
86
+ {
87
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[1mbold\x1b[22m \x1b[2mdim\x1b[0m \x1b[4munder\x1b[0m \x1b[9mstrike\x1b[0m");
88
+ eq(t, [
89
+ { text: "bold", bold: true },
90
+ { text: " " },
91
+ { text: "dim", dim: true },
92
+ { text: " " },
93
+ { text: "under", underline: true },
94
+ { text: " " },
95
+ { text: "strike", strike: true },
96
+ ], "bold/dim/underline/strike attributes");
97
+ }
98
+ // ── bare \x1b[m resets (the opencode banner emits this constantly) ─────
99
+ {
100
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[0m\n> build · deepseek-v4-flash\n\x1b[0m\n");
101
+ eq(t, [
102
+ { text: "\n> build · deepseek-v4-flash\n\n" },
103
+ ], "bare [0m sequences are dropped, text survives");
104
+ }
105
+ // ── $ prompt lines in a real opencode run ───────────────────────────────
106
+ {
107
+ const raw = "\x1b[0m\n> build · deepseek-v4-flash\n\x1b[0m\n\x1b[0m$ \x1b[0mls -la\n";
108
+ const t = (0, ansi_1.tokenizeAnsi)(raw);
109
+ eq((0, ansi_1.ansiToPlain)(raw), "\n> build · deepseek-v4-flash\n\n$ ls -la\n", "plain extraction matches the visible console text");
110
+ const lines = (0, ansi_1.tokenStreamToLines)(t);
111
+ ok(lines.length >= 4, "token stream splits into lines at newlines");
112
+ const last = lines[lines.length - 1];
113
+ ok(last.tokens.some((tk) => tk.text.includes("$ ls -la")), "last line carries the $ prompt text");
114
+ }
115
+ // ── OSC-8 hyperlink extraction ──────────────────────────────────────────
116
+ {
117
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b]8;;https://example.com\x07link text\x1b]8;;\x07 rest");
118
+ eq(t[0]?.href, "https://example.com", "OSC-8 link URL carried on the token");
119
+ eq(t[0]?.text, "link text", "OSC-8 link inner text kept");
120
+ eq(t[1]?.text, " rest", "text after link kept unstyled");
121
+ }
122
+ // ── OSC-8 scheme allowlist (the audit XSS finding, §6.1) ───────────────
123
+ // A prompt-injected \x1b]8;;javascript:…\x07 link used to reach the renderer
124
+ // verbatim and become an <a href> in the dashboard origin. The tokenizer now
125
+ // drops every scheme except http(s):// and mailto:// — the link becomes plain
126
+ // text, never a clickable/executable URL.
127
+ {
128
+ const evil = "\x1b]8;;javascript:alert(1)\x07click\x1b]8;;\x07";
129
+ const t = (0, ansi_1.tokenizeAnsi)(evil);
130
+ eq(t[0]?.href, undefined, "javascript: OSC-8 link is not carried as href");
131
+ eq(t[0]?.text, "click", "the inner text survives as plain text");
132
+ const data = (0, ansi_1.tokenizeAnsi)("\x1b]8;;data:text/html,<script>1</script>\x07x\x1b]8;;\x07");
133
+ eq(data[0]?.href, undefined, "data: OSC-8 link is dropped");
134
+ const proto = (0, ansi_1.tokenizeAnsi)("\x1b]8;;//evil.example/path\x07x\x1b]8;;\x07");
135
+ eq(proto[0]?.href, undefined, "protocol-relative OSC-8 link is dropped");
136
+ const bare = (0, ansi_1.tokenizeAnsi)("\x1b]8;;alert(1)\x07x\x1b]8;;\x07");
137
+ eq(bare[0]?.href, undefined, "scheme-less OSC-8 string is dropped");
138
+ const mail = (0, ansi_1.tokenizeAnsi)("\x1b]8;;mailto:a@b.co\x07mail\x1b]8;;\x07");
139
+ eq(mail[0]?.href, "mailto:a@b.co", "mailto: OSC-8 link is allowed");
140
+ }
141
+ // ── cursor/erase sequences become line breaks (TUI repaint) ─────────────
142
+ {
143
+ const t = (0, ansi_1.tokenizeAnsi)("line1\x1b[2Kline2");
144
+ const plain = t.map((x) => x.text).join("");
145
+ ok(plain.includes("\n"), "erase-line (K) leaves a line boundary for a repaint");
146
+ }
147
+ // ── unknown SGR codes are ignored, text survives ────────────────────────
148
+ {
149
+ const t = (0, ansi_1.tokenizeAnsi)("\x1b[999mweird\x1b[0m ok");
150
+ eq(t, [
151
+ { text: "weird ok" },
152
+ ], "unknown SGR code ignored, adjacent runs merge (no visible style change)");
153
+ }
154
+ if (failures > 0) {
155
+ console.error(`\n${failures} FAILURE(s)`);
156
+ process.exit(1);
157
+ }
158
+ // ── structural console-line classification ──────────────────────────────
159
+ {
160
+ eq((0, ansi_1.classifyAnsiLine)("> build · deepseek-v4-flash"), "banner", "opencode build banner");
161
+ eq((0, ansi_1.classifyAnsiLine)("> plan · gpt-5.4"), "banner", "opencode plan banner");
162
+ eq((0, ansi_1.classifyAnsiLine)("$ ls -la"), "prompt", "$ prompt");
163
+ eq((0, ansi_1.classifyAnsiLine)("$"), "plain", "lone $ is plain (no command)");
164
+ eq((0, ansi_1.classifyAnsiLine)("diff --git a/x.ts b/x.ts"), "diff-header", "git diff header");
165
+ eq((0, ansi_1.classifyAnsiLine)("+++ b/src/x.ts"), "diff-file", "+++ file line");
166
+ eq((0, ansi_1.classifyAnsiLine)("--- a/src/x.ts"), "diff-file", "--- file line");
167
+ eq((0, ansi_1.classifyAnsiLine)("@@ -1,2 +1,3 @@"), "diff-hunk", "hunk header");
168
+ eq((0, ansi_1.classifyAnsiLine)("+const x = 1;"), "diff-add", "patch add");
169
+ eq((0, ansi_1.classifyAnsiLine)("-const y = 2;"), "diff-del", "patch del");
170
+ eq((0, ansi_1.classifyAnsiLine)(" + indented keeps leading"), "plain", "indented + is not a patch add");
171
+ eq((0, ansi_1.classifyAnsiLine)("⎿ run npx tsc"), "tool-call", "opencode tool tail");
172
+ eq((0, ansi_1.classifyAnsiLine)("✓ 3 files changed"), "tool-call", "checkmark status");
173
+ eq((0, ansi_1.classifyAnsiLine)("normal output line"), "plain", "ordinary line");
174
+ }
175
+ {
176
+ const styled = (0, ansi_1.styleAnsiLines)("\x1b[0m\n> build · deepseek-v4-flash\n\x1b[0m\n$ \x1b[0mls\n");
177
+ ok(styled.length >= 4, "styleAnsiLines splits into lines");
178
+ const hints = styled.map((l) => l.hint);
179
+ ok(hints.includes("banner"), "banner line classified through the one-stop helper");
180
+ ok(hints.includes("prompt"), "$ prompt classified through the one-stop helper");
181
+ }
182
+ // ── summarizeRawConsole (shared noisy-console reducer) ──────────────────
183
+ {
184
+ const raw = [
185
+ "> build · deepseek-v4-flash", // banner — kept
186
+ "$ npm run build", // prompt echo — dropped
187
+ "workdir: /repo", // runner config banner — dropped
188
+ "compiled 42 files", // real output — kept
189
+ "compiled 42 files", // repeat
190
+ "compiled 42 files", // 3rd repeat — collapsed
191
+ "compiled 42 files", // 4th — dropped
192
+ "diff --git a/x.ts b/x.ts", // diff hunk — dropped
193
+ "──────────", // TUI redraw edge — dropped
194
+ "✓ done", // status — kept
195
+ ].join("\n");
196
+ const out = (0, ansi_1.summarizeRawConsole)(raw, false);
197
+ ok(out.includes("> build · deepseek-v4-flash"), "banner survives");
198
+ ok(!out.includes("$ npm run build"), "$ echo dropped");
199
+ ok(!out.includes("workdir:"), "config banner dropped");
200
+ ok(out.includes("compiled 42 files"), "real output kept");
201
+ ok(!out.includes("diff --git"), "diff hunk dropped");
202
+ ok(!out.includes("──────────"), "TUI redraw edge dropped");
203
+ ok(out.includes("✓ done"), "status kept");
204
+ ok(out.includes("noisy lines collapsed"), "collapse count reported");
205
+ // Running budget is tighter than finished.
206
+ const big = Array.from({ length: 200 }, (_, i) => `line ${i}`).join("\n");
207
+ const running = (0, ansi_1.summarizeRawConsole)(big, true).split("\n").length;
208
+ const finished = (0, ansi_1.summarizeRawConsole)(big, false).split("\n").length;
209
+ ok(running < finished, "running budget tighter than finished budget");
210
+ // KEEP THE TAIL (2026-08-13): the summarizer evicts the OLDEST kept line
211
+ // when the budget is full, so a live console follows the newest output —
212
+ // never drops it. The old head-keeping bug froze the web task console at
213
+ // the START of a long run while the header claimed it was live.
214
+ const tail = (0, ansi_1.summarizeRawConsole)(big, true).split("\n");
215
+ ok(tail.some((l) => l.startsWith("line 199")), "newest output survives the budget (tail kept)");
216
+ ok(!tail.some((l) => l.startsWith("line 0")), "oldest output evicted to make room");
217
+ // The tail must still be recognizably the END of the stream, and the
218
+ // collapse marker must not masquerade as a kept line.
219
+ const noMarker = tail.filter((l) => !l.startsWith("… "));
220
+ ok(noMarker[0].startsWith("line 160"), "first kept line is the oldest evicted window start (budget 40 of 200)");
221
+ // ANSI on kept lines survives (so AnsiConsoleText can still paint them).
222
+ const ansiLine = "\x1b[31mred text\x1b[0m";
223
+ ok((0, ansi_1.summarizeRawConsole)(ansiLine, false).includes("\x1b[31m"), "kept lines keep their escapes");
224
+ }
225
+ console.log("\nall ansi tests pass");
@@ -1,13 +1,8 @@
1
- export interface BuildFeedbackPromptInput {
1
+ export interface FeedbackPromptInput {
2
2
  userPrompt: string;
3
- /** Hot-Reload project name when running inside Yaver mobile, OR
4
- * the host app's bundle/package name when running standalone. */
5
3
  projectName?: string;
6
- /** Absolute path on the host where the project lives (only known
7
- * when running inside Yaver mobile via Hot Reload). */
8
4
  projectPath?: string;
9
- /** True when the caller has attached a screenshot of the current
10
- * screen as the first image in the task's images array. */
11
- hasScreenshot: boolean;
5
+ hasScreenshot?: boolean;
12
6
  }
13
- export declare function buildFeedbackPrompt(input: BuildFeedbackPromptInput): string;
7
+ /** Shared, deterministic feedback-task prompt used by every client surface. */
8
+ export declare function buildFeedbackPrompt(input: FeedbackPromptInput): string;
@@ -1,77 +1,23 @@
1
1
  "use strict";
2
- // buildFeedbackPrompt shared prompt enrichment used by every Yaver
3
- // feedback surface, in-Yaver native pane (mirrored in Swift + Kotlin)
4
- // AND the standalone RN feedback SDK (this file). Keep all three
5
- // implementations in lockstep — the wording is what the AI on the
6
- // remote is conditioned to expect.
7
- //
8
- // The bare user text on its own loses crucial context: WHICH app the
9
- // user is testing, WHICH screen they're looking at, and whether a
10
- // screenshot is attached for visual reference. Without that the agent
11
- // guesses, edits the wrong project, or asks clarifying questions
12
- // instead of acting. The wrapper below tells the agent:
13
- // - this feedback comes from the in-app drawer while the user is
14
- // mid-test,
15
- // - which project the user is in (when known),
16
- // - that the FIRST attached image (when present) is a snapshot of
17
- // the current screen — open it to see what the user is pointing
18
- // at,
19
- // - that changes should be applied to that project's source +
20
- // saved so the user can trigger a Hermes reload to see them.
21
- //
22
- // Cross-reference: mobile/ios/Yaver/YaverFeedbackPane.swift's
23
- // `buildFeedbackPrompt` and mobile/android/.../YaverFeedbackPane.kt's
24
- // `buildFeedbackPrompt`. All three must match.
2
+ // AUTO-SYNCED from shared/client-core/src/buildFeedbackPrompt.ts.
3
+ // DO NOT EDIT IN PLACE. Edit the source and re-run
4
+ // scripts/sync-client-core.sh. CI checks drift via `--check`.
25
5
  Object.defineProperty(exports, "__esModule", { value: true });
26
6
  exports.buildFeedbackPrompt = buildFeedbackPrompt;
7
+ /** Shared, deterministic feedback-task prompt used by every client surface. */
27
8
  function buildFeedbackPrompt(input) {
28
- const userPrompt = input.userPrompt ?? "";
29
- const projectName = (input.projectName ?? "").trim();
30
- const projectPath = (input.projectPath ?? "").trim();
31
- const hasScreenshot = !!input.hasScreenshot;
32
- const lines = [];
33
- lines.push("[Mobile feedback from inside Yaver]");
34
- lines.push("The user is providing this feedback while running a mobile app inside the Yaver mobile container " +
35
- "and is currently looking at a specific screen of that app.");
36
- lines.push("");
37
- if (projectName || projectPath) {
38
- lines.push("App being tested:");
39
- if (projectName)
40
- lines.push(` name: ${projectName}`);
41
- if (projectPath)
42
- lines.push(` path: ${projectPath}`);
43
- lines.push("");
44
- }
45
- if (hasScreenshot) {
46
- lines.push("A screenshot of the current screen is attached as the first image. " +
47
- "Open it before deciding what to change — the user is pointing at what they SEE, " +
48
- "not necessarily what is named most prominently in the source.");
49
- lines.push("");
50
- }
51
- else {
52
- lines.push("(The user chose not to attach a screenshot for this round.)");
53
- lines.push("");
54
- }
55
- lines.push("Operation contract:");
56
- lines.push("1. Locate the file(s) responsible for what the user described and EDIT them in place. " +
57
- "Save the changes — that is the deliverable.");
58
- lines.push("2. Stream a CONCISE Claude-Code / Codex-style narration as you work: " +
59
- "one short line per step (e.g. \"Reading app/index.tsx\", " +
60
- "\"Editing safe.backgroundColor\", \"Saved app/index.tsx\"). Show small diffs only — " +
61
- "never dump entire files, never paste node_modules contents, never echo build / install logs.");
62
- lines.push("3. Do NOT run npm install / yarn / pnpm / git clone / cargo build / docker pull or any other " +
63
- "long-running install / fetch command. The repo is already prepared on this machine. " +
64
- "If a dependency is genuinely missing, say so in one line and stop — the user will install it.");
65
- lines.push("4. Do NOT trigger a Hermes reload yourself. The user has a Reload button in the drawer " +
66
- "and decides when to refresh.");
67
- lines.push("5. Keep total output under a few hundred lines. Heavy ripgrep / find / cat with no filter " +
68
- "are usually the wrong tool — use targeted reads.");
69
- if (!projectName && !projectPath) {
70
- lines.push("6. If you can identify the project from the prompt or the screenshot, work there. " +
71
- "Otherwise ask the user briefly which project to target — one short line, no exhaustive list.");
72
- }
73
- lines.push("");
74
- lines.push("User feedback:");
75
- lines.push(userPrompt);
76
- return lines.join("\n");
9
+ const request = String(input.userPrompt || '').trim();
10
+ const context = [];
11
+ if (input.projectName?.trim())
12
+ context.push(`Project: ${input.projectName.trim()}`);
13
+ if (input.projectPath?.trim())
14
+ context.push(`Working directory: ${input.projectPath.trim()}`);
15
+ if (input.hasScreenshot)
16
+ context.push('A screenshot is attached; use it as visual evidence.');
17
+ return [
18
+ 'Investigate and implement this feedback request in the named project.',
19
+ ...context,
20
+ '',
21
+ request || 'Inspect the attached evidence and fix the visible issue.',
22
+ ].join('\n');
77
23
  }
@@ -27,13 +27,26 @@ export declare const DEFAULT_BEACON_UDP_PORT = 19837;
27
27
  /**
28
28
  * How old an agent's last heartbeat can be before the device is
29
29
  * considered offline. Mirrors `backend/convex/devices.ts` so
30
- * Convex + every client agree on the same threshold. The agent
31
- * heartbeats every 5 min (see `desktop/agent/main.go::heartbeatLoop`),
32
- * so 6 min tolerates one missed beat + 60 s of jitter without
33
- * flapping. Sub-minute death detection comes from the P2P bus, not
34
- * this threshold.
30
+ * Convex + every client agree on the same threshold.
31
+ *
32
+ * 900_000, not 90_000. This file said 90_000 while the authority named in the
33
+ * comment `backend/convex/devices.ts:112`, `const HEARTBEAT_STALE_MS = 900 *
34
+ * 1000` — and `mobile/src/_core/constants.ts` both said 900_000. A 10x drift in
35
+ * the constant whose own docstring promises it does not drift is exactly the
36
+ * "green on one, yellow on the other" glitch described above (2026-07-20).
37
+ *
38
+ * Sub-minute death detection is not this threshold's job — it comes from the
39
+ * P2P bus and from an actual reachability probe. Shortening this here would
40
+ * only make a live box flap offline between 5-minute heartbeats.
41
+ */
42
+ export declare const HEARTBEAT_STALE_MS = 900000;
43
+ /**
44
+ * How long after the last relay-presence ping a device still counts as
45
+ * having a "live bus signal". Same product number as the web dashboard's
46
+ * hasRecentLiveSignal default (`web/lib/device-lifecycle.ts`, maxAgeMs =
47
+ * 360_000) so the phone and the browser agree on what "live" means.
35
48
  */
36
- export declare const HEARTBEAT_STALE_MS = 360000;
49
+ export declare const BUS_PRESENCE_STALE_MS = 360000;
37
50
  /**
38
51
  * How long after the last UDP beacon an agent is still considered
39
52
  * "locally present". Re-broadcast interval is 3 s, so 10 s covers
@@ -3,7 +3,7 @@
3
3
  // DO NOT EDIT IN PLACE. Edit the source and re-run
4
4
  // scripts/sync-client-core.sh. CI checks drift via `--check`.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OAUTH_REDIRECT = exports.RELAY_PROBE_TIMEOUT_MS = exports.PROBE_TIMEOUT_MS = exports.BEACON_STALE_MS = exports.HEARTBEAT_STALE_MS = exports.DEFAULT_BEACON_UDP_PORT = exports.DEFAULT_AGENT_HTTP_PORT = exports.WEB_BASE_URL = exports.CONVEX_SITE_URL = void 0;
6
+ exports.OAUTH_REDIRECT = exports.RELAY_PROBE_TIMEOUT_MS = exports.PROBE_TIMEOUT_MS = exports.BEACON_STALE_MS = exports.BUS_PRESENCE_STALE_MS = exports.HEARTBEAT_STALE_MS = exports.DEFAULT_BEACON_UDP_PORT = exports.DEFAULT_AGENT_HTTP_PORT = exports.WEB_BASE_URL = exports.CONVEX_SITE_URL = void 0;
7
7
  /**
8
8
  * Canonical client-side constants shared between:
9
9
  * - Yaver mobile (`mobile/`)
@@ -51,13 +51,26 @@ exports.DEFAULT_BEACON_UDP_PORT = 19837;
51
51
  /**
52
52
  * How old an agent's last heartbeat can be before the device is
53
53
  * considered offline. Mirrors `backend/convex/devices.ts` so
54
- * Convex + every client agree on the same threshold. The agent
55
- * heartbeats every 5 min (see `desktop/agent/main.go::heartbeatLoop`),
56
- * so 6 min tolerates one missed beat + 60 s of jitter without
57
- * flapping. Sub-minute death detection comes from the P2P bus, not
58
- * this threshold.
54
+ * Convex + every client agree on the same threshold.
55
+ *
56
+ * 900_000, not 90_000. This file said 90_000 while the authority named in the
57
+ * comment `backend/convex/devices.ts:112`, `const HEARTBEAT_STALE_MS = 900 *
58
+ * 1000` — and `mobile/src/_core/constants.ts` both said 900_000. A 10x drift in
59
+ * the constant whose own docstring promises it does not drift is exactly the
60
+ * "green on one, yellow on the other" glitch described above (2026-07-20).
61
+ *
62
+ * Sub-minute death detection is not this threshold's job — it comes from the
63
+ * P2P bus and from an actual reachability probe. Shortening this here would
64
+ * only make a live box flap offline between 5-minute heartbeats.
65
+ */
66
+ exports.HEARTBEAT_STALE_MS = 900000;
67
+ /**
68
+ * How long after the last relay-presence ping a device still counts as
69
+ * having a "live bus signal". Same product number as the web dashboard's
70
+ * hasRecentLiveSignal default (`web/lib/device-lifecycle.ts`, maxAgeMs =
71
+ * 360_000) so the phone and the browser agree on what "live" means.
59
72
  */
60
- exports.HEARTBEAT_STALE_MS = 360000;
73
+ exports.BUS_PRESENCE_STALE_MS = 360000;
61
74
  /**
62
75
  * How long after the last UDP beacon an agent is still considered
63
76
  * "locally present". Re-broadcast interval is 3 s, so 10 s covers
@@ -10,10 +10,6 @@ export interface CoreDevice {
10
10
  runnerDown: boolean;
11
11
  /** Unix ms of the latest heartbeat the agent sent to Convex. */
12
12
  lastHeartbeat: number;
13
- isGuest: boolean;
14
- hostName?: string;
15
- hostEmail?: string;
16
- accessScope?: 'owner' | 'shared-scoped' | 'shared-legacy';
17
13
  /** Primary LAN IP (or tunnel host) the agent advertised. */
18
14
  quicHost: string;
19
15
  quicPort: number;
@@ -47,7 +43,7 @@ export declare function collapseDevices(devices: CoreDevice[]): CoreDevice[];
47
43
  export declare function isDeviceFresh(d: CoreDevice, now?: number): boolean;
48
44
  /**
49
45
  * Choose the best candidate for an auto-connect attempt. Preference:
50
- * 1. explicit `preferredDeviceId` that's still fresh
46
+ * 1. explicit `preferredDeviceId`, by identity alone; missing means fail
51
47
  * 2. fresh (online + recent heartbeat) + has a quicHost
52
48
  * 3. online + has a quicHost
53
49
  * 4. first with a quicHost
@@ -50,10 +50,6 @@ function deviceIdentityKey(d) {
50
50
  return `hwid:${d.hwid}`;
51
51
  if (d.publicKey)
52
52
  return `pub:${d.publicKey}`;
53
- if (d.isGuest) {
54
- const scope = d.hostEmail || d.hostName || 'guest';
55
- return `guest:${scope}:${d.deviceId || d.name}`;
56
- }
57
53
  const n = normName(d.name);
58
54
  const os = String(d.platform || '').trim().toLowerCase();
59
55
  if (n && os)
@@ -63,8 +59,6 @@ function deviceIdentityKey(d) {
63
59
  return `name:${d.name}`;
64
60
  }
65
61
  function deviceAliasKey(d) {
66
- if (d.isGuest)
67
- return null;
68
62
  const n = normName(d.name);
69
63
  const os = String(d.platform || '').trim().toLowerCase();
70
64
  if (!n || !os)
@@ -72,8 +66,6 @@ function deviceAliasKey(d) {
72
66
  return `${os}:${n}`;
73
67
  }
74
68
  function deviceEndpointKey(d) {
75
- if (d.isGuest)
76
- return null;
77
69
  const h = normHost(d.quicHost);
78
70
  if (!h)
79
71
  return null;
@@ -196,7 +188,7 @@ function isDeviceFresh(d, now = Date.now()) {
196
188
  }
197
189
  /**
198
190
  * Choose the best candidate for an auto-connect attempt. Preference:
199
- * 1. explicit `preferredDeviceId` that's still fresh
191
+ * 1. explicit `preferredDeviceId`, by identity alone; missing means fail
200
192
  * 2. fresh (online + recent heartbeat) + has a quicHost
201
193
  * 3. online + has a quicHost
202
194
  * 4. first with a quicHost
@@ -205,11 +197,10 @@ function pickTargetDevice(devices, preferredDeviceId) {
205
197
  if (!devices.length)
206
198
  return null;
207
199
  if (preferredDeviceId) {
208
- const preferred = devices.find((d) => d.deviceId === preferredDeviceId && d.quicHost);
209
- if (preferred && isDeviceFresh(preferred))
210
- return preferred;
211
- if (preferred)
212
- return preferred;
200
+ // An off-LAN device normally has no quicHost; the relay addresses it by
201
+ // id. Never fall through to a different healthy machine after the user
202
+ // selected one explicitly — failure is safer than misrouting their work.
203
+ return devices.find((d) => d.deviceId === preferredDeviceId) || null;
213
204
  }
214
205
  const fresh = devices.find((d) => isDeviceFresh(d) && d.quicHost);
215
206
  if (fresh)
@@ -51,13 +51,6 @@ export declare const CONVEX_ENDPOINTS: {
51
51
  readonly authLogin: "/auth/login";
52
52
  readonly userSettings: "/settings";
53
53
  readonly platformConfig: "/config";
54
- readonly guestsList: "/guests/list";
55
- readonly guestsHosts: "/guests/hosts";
56
- readonly guestsAllowed: "/guests/allowed";
57
- readonly guestsInvite: "/guests/invite";
58
- readonly guestsAccept: "/guests/accept";
59
- readonly guestsAcceptCode: "/guests/accept-code";
60
- readonly guestsRevoke: "/guests/revoke";
61
54
  };
62
55
  /** Routes on a relay server. */
63
56
  export declare const RELAY_ENDPOINTS: {
@@ -57,13 +57,6 @@ exports.CONVEX_ENDPOINTS = {
57
57
  authLogin: '/auth/login',
58
58
  userSettings: '/settings',
59
59
  platformConfig: '/config',
60
- guestsList: '/guests/list',
61
- guestsHosts: '/guests/hosts',
62
- guestsAllowed: '/guests/allowed',
63
- guestsInvite: '/guests/invite',
64
- guestsAccept: '/guests/accept',
65
- guestsAcceptCode: '/guests/accept-code',
66
- guestsRevoke: '/guests/revoke',
67
60
  };
68
61
  /** Routes on a relay server. */
69
62
  exports.RELAY_ENDPOINTS = {
@@ -13,3 +13,7 @@
13
13
  export * from './constants';
14
14
  export * from './endpoints';
15
15
  export * from './device';
16
+ export * from './ansi';
17
+ export * from './trace';
18
+ export * from './remoteless';
19
+ export * from './buildFeedbackPrompt';
@@ -32,3 +32,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
32
32
  __exportStar(require("./constants"), exports);
33
33
  __exportStar(require("./endpoints"), exports);
34
34
  __exportStar(require("./device"), exports);
35
+ __exportStar(require("./ansi"), exports);
36
+ __exportStar(require("./trace"), exports);
37
+ __exportStar(require("./remoteless"), exports);
38
+ __exportStar(require("./buildFeedbackPrompt"), exports);
@@ -0,0 +1,44 @@
1
+ export type RemotelessCapability = "analysis-chat" | "code-edit" | "git-read" | "git-commit" | "git-push" | "static-preflight" | "existing-web-artifact" | "dev-server" | "web-build" | "flutter-render" | "native-build" | "simulator" | "shell" | "test" | "deploy" | "container";
2
+ export type RemotelessSupport = "supported" | "bounded" | "unavailable";
3
+ export type RemotelessCapabilityResult = {
4
+ capability: RemotelessCapability;
5
+ support: RemotelessSupport;
6
+ code: string;
7
+ summary: string;
8
+ detail: string;
9
+ route: {
10
+ label: string;
11
+ path: "/devices" | "/cloud-onboarding";
12
+ };
13
+ alternateRoute?: {
14
+ label: string;
15
+ path: "/devices" | "/cloud-onboarding";
16
+ };
17
+ };
18
+ export type ExecutionCandidate = {
19
+ id: string;
20
+ name: string;
21
+ role: "explicit" | "primary" | "secondary" | "focused";
22
+ connected: boolean;
23
+ };
24
+ export type RemotelessPlacement = {
25
+ lane: "remote";
26
+ target: ExecutionCandidate;
27
+ degraded: boolean;
28
+ banner: string | null;
29
+ } | {
30
+ lane: "remoteless";
31
+ capability: RemotelessCapabilityResult;
32
+ banner: string;
33
+ } | {
34
+ lane: "blocked";
35
+ capability: RemotelessCapabilityResult;
36
+ banner: string;
37
+ };
38
+ export declare function remotelessCapability(capability: RemotelessCapability, surface: "ios" | "android" | "web" | "companion"): RemotelessCapabilityResult;
39
+ export declare function resolveRemotelessPlacement(input: {
40
+ capability: RemotelessCapability;
41
+ surface: "ios" | "android" | "web" | "companion";
42
+ candidates: ExecutionCandidate[];
43
+ forceLocal?: boolean;
44
+ }): RemotelessPlacement;