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
|
@@ -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
|
|
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
|
-
|
|
10
|
-
* screen as the first image in the task's images array. */
|
|
11
|
-
hasScreenshot: boolean;
|
|
5
|
+
hasScreenshot?: boolean;
|
|
12
6
|
}
|
|
13
|
-
|
|
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
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
|
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
|
package/dist/_core/constants.js
CHANGED
|
@@ -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.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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.
|
|
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
|
package/dist/_core/device.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/_core/device.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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: {
|
package/dist/_core/endpoints.js
CHANGED
|
@@ -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 = {
|
package/dist/_core/index.d.ts
CHANGED
package/dist/_core/index.js
CHANGED
|
@@ -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;
|