@bivy/bivy 0.18.10-staging.4 → 0.18.11-staging.1
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/dist/apps/gateway.js +6 -2
- package/dist/apps/preview-shell.js +7 -0
- package/dist/apps/remote-preview.js +4 -2
- package/dist/config-cli.js +2 -0
- package/dist/node-config.js +6 -3
- package/dist/server.js +26 -2
- package/package.json +1 -1
package/dist/apps/gateway.js
CHANGED
|
@@ -195,6 +195,7 @@ export class AppGateway {
|
|
|
195
195
|
returnOrigins;
|
|
196
196
|
signIn;
|
|
197
197
|
captureNote;
|
|
198
|
+
badge;
|
|
198
199
|
server;
|
|
199
200
|
template;
|
|
200
201
|
tickets = new Map();
|
|
@@ -211,11 +212,12 @@ export class AppGateway {
|
|
|
211
212
|
displays = new WebSocketServer({ noServer: true, maxPayload: 1024 * 1024, perMessageDeflate: false });
|
|
212
213
|
/** `signIn` sends a signed-out visit to a view's stable address back
|
|
213
214
|
* through Bivy, which re-opens it for a signed-in device. */
|
|
214
|
-
constructor(registry, originTemplate, returnOrigins = () => [], signIn, captureNote) {
|
|
215
|
+
constructor(registry, originTemplate, returnOrigins = () => [], signIn, captureNote, badge) {
|
|
215
216
|
this.registry = registry;
|
|
216
217
|
this.returnOrigins = returnOrigins;
|
|
217
218
|
this.signIn = signIn;
|
|
218
219
|
this.captureNote = captureNote;
|
|
220
|
+
this.badge = badge;
|
|
219
221
|
this.template = previewOriginTemplate(originTemplate);
|
|
220
222
|
this.server = http.createServer((req, res) => { void this.handle(req, res).catch(() => { if (!res.headersSent)
|
|
221
223
|
res.writeHead(502); res.end("Preview request failed."); }); });
|
|
@@ -365,7 +367,9 @@ export class AppGateway {
|
|
|
365
367
|
return;
|
|
366
368
|
}
|
|
367
369
|
res.setHeader("Content-Type", "application/json");
|
|
368
|
-
|
|
370
|
+
// Someone opening a shared link sees "Made with Bivy" under the app; the owner doesn't.
|
|
371
|
+
const badge = grant.reusable && this.badge ? await this.badge().catch(() => true) : false;
|
|
372
|
+
res.end(JSON.stringify({ name: entry.app.name, origin: this.origin(id), returnTo: grant.returnTo, ...(grant.reusable ? { reviewer: true } : {}), ...(badge ? { badge: true } : {}), ...(entry.target.kind === "display" ? { inspect: false } : {}) }));
|
|
369
373
|
return;
|
|
370
374
|
}
|
|
371
375
|
res.writeHead(404);
|
|
@@ -81,11 +81,16 @@ nav .btn[aria-pressed="true"] { background:var(--accent-soft); color:var(--accen
|
|
|
81
81
|
#compare[data-drawing] #compare-slider, #compare[data-drawing] #compare-hint, #compare[data-drawing] #compare-draw { display:none; }
|
|
82
82
|
.label-narrow { display:none; }
|
|
83
83
|
@media (max-width: 699px) { #lens { display:none; } #name { display:none; } .label-wide { display:none; } .label-narrow { display:inline; } }
|
|
84
|
+
/* "Made with Bivy": a quiet strip under a shared app, never over it. */
|
|
85
|
+
#made-with { flex-shrink:0; display:flex; align-items:center; justify-content:center; gap:var(--space-1); min-height:var(--space-6); padding:0 var(--space-3) env(safe-area-inset-bottom); box-sizing:border-box; background:var(--surface); border-top:thin solid var(--line); color:var(--muted); font-size:var(--text-xs); text-decoration:none; }
|
|
86
|
+
#made-with:hover, #made-with:focus-visible { color:var(--ink); }
|
|
87
|
+
body.badged #dock { bottom:calc(var(--space-2) + var(--space-6) + env(safe-area-inset-bottom)); }
|
|
84
88
|
[hidden] { display:none !important; }
|
|
85
89
|
</style></head><body>
|
|
86
90
|
<div class="banner" data-tone="warn" id="down" hidden><span class="banner-text" id="down-text" role="status"></span><span class="banner-actions"><button class="btn sm" id="ask" hidden>Ask agent to fix</button></span></div>
|
|
87
91
|
<p id="status" role="status">Opening app…</p>
|
|
88
92
|
<div id="stage" data-lens="full"><iframe id="app" title="App preview" hidden sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-popups" referrerpolicy="no-referrer"></iframe></div>
|
|
93
|
+
<a id="made-with" href="https://bivy.sh/?ref=preview" target="_blank" rel="noopener" hidden>Made with <strong>Bivy</strong><span aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
|
89
94
|
<div id="ink" hidden aria-hidden="true"><svg id="ink-marks"></svg></div>
|
|
90
95
|
<div id="dock">
|
|
91
96
|
<section class="panel" id="console" hidden aria-labelledby="console-title">
|
|
@@ -157,6 +162,8 @@ function show(data,launch){
|
|
|
157
162
|
$('draft-text').maxLength=1000;
|
|
158
163
|
$('send-errors').textContent='Leave a note…';
|
|
159
164
|
}
|
|
165
|
+
$('made-with').hidden=!data.badge;
|
|
166
|
+
document.body.classList.toggle('badged',Boolean(data.badge));
|
|
160
167
|
$('title').textContent=data.name;
|
|
161
168
|
$('title').title=data.name;
|
|
162
169
|
document.title='Bivy · '+data.name;
|
|
@@ -10,16 +10,18 @@ export class RemotePreview {
|
|
|
10
10
|
returnOrigins;
|
|
11
11
|
signIn;
|
|
12
12
|
captureNote;
|
|
13
|
+
badge;
|
|
13
14
|
gateway;
|
|
14
15
|
origin;
|
|
15
16
|
relayUrl;
|
|
16
17
|
online = false;
|
|
17
18
|
sockets = new Set();
|
|
18
|
-
constructor(registry, returnOrigins, signIn, captureNote) {
|
|
19
|
+
constructor(registry, returnOrigins, signIn, captureNote, badge) {
|
|
19
20
|
this.registry = registry;
|
|
20
21
|
this.returnOrigins = returnOrigins;
|
|
21
22
|
this.signIn = signIn;
|
|
22
23
|
this.captureNote = captureNote;
|
|
24
|
+
this.badge = badge;
|
|
23
25
|
}
|
|
24
26
|
get available() { return this.online && Boolean(this.gateway); }
|
|
25
27
|
ready(origin, relayUrl) {
|
|
@@ -32,7 +34,7 @@ export class RemotePreview {
|
|
|
32
34
|
return;
|
|
33
35
|
if (this.origin !== template) {
|
|
34
36
|
this.gateway?.close();
|
|
35
|
-
this.gateway = new AppGateway(this.registry, template, this.returnOrigins, this.signIn, this.captureNote);
|
|
37
|
+
this.gateway = new AppGateway(this.registry, template, this.returnOrigins, this.signIn, this.captureNote, this.badge);
|
|
36
38
|
this.origin = template;
|
|
37
39
|
}
|
|
38
40
|
this.relayUrl = relayUrl.replace(/\/$/, "") + "/preview/stream";
|
package/dist/config-cli.js
CHANGED
|
@@ -109,6 +109,7 @@ const ENV_FOR_KEY = {
|
|
|
109
109
|
"defaults.approval": "BIVY_APPROVAL_MODE",
|
|
110
110
|
"sessions.autoAttachToolImages": "BIVY_AUTO_ATTACH_TOOL_IMAGES",
|
|
111
111
|
"sessions.appScreenshots": "BIVY_APP_SCREENSHOTS",
|
|
112
|
+
"sessions.previewBadge": "BIVY_PREVIEW_BADGE",
|
|
112
113
|
"automation.checks": "BIVY_AUTOMATION_CHECKS",
|
|
113
114
|
"automation.checkTimeoutMinutes": "BIVY_AUTOMATION_CHECK_TIMEOUT_MS",
|
|
114
115
|
};
|
|
@@ -124,6 +125,7 @@ const BUILTIN_VALUES = {
|
|
|
124
125
|
"sessions.resume": "auto",
|
|
125
126
|
"sessions.autoAttachToolImages": false,
|
|
126
127
|
"sessions.appScreenshots": false,
|
|
128
|
+
"sessions.previewBadge": true,
|
|
127
129
|
"automation.checks": ["test", "lint", "typecheck"],
|
|
128
130
|
"automation.checkTimeoutMinutes": 10,
|
|
129
131
|
};
|
package/dist/node-config.js
CHANGED
|
@@ -74,7 +74,7 @@ export function validateNodeConfig(value) {
|
|
|
74
74
|
const node = section(root, "node", ["workspace", "port", "maxConcurrentAutomations", "capabilities"], errors);
|
|
75
75
|
const defaults = section(root, "defaults", ["agent", "model", "sandbox", "approval"], errors);
|
|
76
76
|
const safety = section(root, "safety", ["maxSandbox", "approvalFloor"], errors);
|
|
77
|
-
const sessions = section(root, "sessions", ["sync", "worktreeSync", "standbyNodeId", "resume", "autoAttachToolImages", "appScreenshots", "forkWorkspaceMaxBytes", "wedgedTurnMinutes"], errors);
|
|
77
|
+
const sessions = section(root, "sessions", ["sync", "worktreeSync", "standbyNodeId", "resume", "autoAttachToolImages", "appScreenshots", "previewBadge", "forkWorkspaceMaxBytes", "wedgedTurnMinutes"], errors);
|
|
78
78
|
const github = section(root, "github", ["issuePrompt"], errors);
|
|
79
79
|
const automation = section(root, "automation", ["checks", "checkTimeoutMinutes"], errors);
|
|
80
80
|
const agentsRaw = section(root, "agents", Object.keys(record(root.agents) ?? {}), errors);
|
|
@@ -134,6 +134,7 @@ export function validateNodeConfig(value) {
|
|
|
134
134
|
errors.push("sessions.resume must be auto or manual");
|
|
135
135
|
const autoAttachToolImages = optionalBoolean(sessions.autoAttachToolImages, "sessions.autoAttachToolImages", errors);
|
|
136
136
|
const appScreenshots = optionalBoolean(sessions.appScreenshots, "sessions.appScreenshots", errors);
|
|
137
|
+
const previewBadge = optionalBoolean(sessions.previewBadge, "sessions.previewBadge", errors);
|
|
137
138
|
const forkWorkspaceMaxBytes = optionalInteger(sessions.forkWorkspaceMaxBytes, "sessions.forkWorkspaceMaxBytes", errors, 1_048_576, 1_073_741_824);
|
|
138
139
|
// Upper bound is the wall-clock turn cap (60 min): a wedged window at/above it
|
|
139
140
|
// would never fire before the cap. 0 disables the band.
|
|
@@ -217,7 +218,7 @@ export function validateNodeConfig(value) {
|
|
|
217
218
|
...(Object.keys(node).length ? { node: { workspace, port, maxConcurrentAutomations, capabilities } } : {}),
|
|
218
219
|
...(Object.keys(defaults).length ? { defaults: { agent, model, sandbox, approval } } : {}),
|
|
219
220
|
...(Object.keys(safety).length ? { safety: { maxSandbox, approvalFloor } } : {}),
|
|
220
|
-
...(Object.keys(sessions).length ? { sessions: { sync, worktreeSync, standbyNodeId, resume, autoAttachToolImages, appScreenshots, forkWorkspaceMaxBytes, wedgedTurnMinutes } } : {}),
|
|
221
|
+
...(Object.keys(sessions).length ? { sessions: { sync, worktreeSync, standbyNodeId, resume, autoAttachToolImages, appScreenshots, previewBadge, forkWorkspaceMaxBytes, wedgedTurnMinutes } } : {}),
|
|
221
222
|
...(Object.keys(github).length ? { github: { issuePrompt } } : {}),
|
|
222
223
|
...(Object.keys(automation).length ? { automation: { checks, checkTimeoutMinutes } } : {}),
|
|
223
224
|
...(Object.keys(agents).length ? { agents } : {}),
|
|
@@ -274,6 +275,7 @@ export function configToLegacySettings(config) {
|
|
|
274
275
|
...(config.sessions?.resume ? { sessionResumeMode: config.sessions.resume } : {}),
|
|
275
276
|
...(config.sessions?.autoAttachToolImages !== undefined ? { autoAttachToolImages: config.sessions.autoAttachToolImages } : {}),
|
|
276
277
|
...(config.sessions?.appScreenshots !== undefined ? { appScreenshots: config.sessions.appScreenshots } : {}),
|
|
278
|
+
...(config.sessions?.previewBadge !== undefined ? { previewBadge: config.sessions.previewBadge } : {}),
|
|
277
279
|
...(config.sessions?.forkWorkspaceMaxBytes !== undefined ? { forkWorkspaceMaxBytes: config.sessions.forkWorkspaceMaxBytes } : {}),
|
|
278
280
|
};
|
|
279
281
|
}
|
|
@@ -338,6 +340,7 @@ export function mergeLegacyIntoNodeConfig(cli, settings) {
|
|
|
338
340
|
resume: settings.sessionResumeMode === "manual" ? "manual" : "auto",
|
|
339
341
|
autoAttachToolImages: settings.autoAttachToolImages === true,
|
|
340
342
|
appScreenshots: settings.appScreenshots === true,
|
|
343
|
+
previewBadge: settings.previewBadge !== false,
|
|
341
344
|
forkWorkspaceMaxBytes: Number.isInteger(settings.forkWorkspaceMaxBytes) ? Number(settings.forkWorkspaceMaxBytes) : undefined,
|
|
342
345
|
},
|
|
343
346
|
github: { issuePrompt: typeof settings.githubIssuePrompt === "string" ? settings.githubIssuePrompt : undefined },
|
|
@@ -364,7 +367,7 @@ export function setConfigValue(config, dotted, value) {
|
|
|
364
367
|
"node.workspace", "node.port", "node.maxConcurrentAutomations", "node.capabilities",
|
|
365
368
|
"defaults.agent", "defaults.model", "defaults.sandbox", "defaults.approval",
|
|
366
369
|
"safety.maxSandbox", "safety.approvalFloor",
|
|
367
|
-
"sessions.sync", "sessions.worktreeSync", "sessions.standbyNodeId", "sessions.resume", "sessions.autoAttachToolImages", "sessions.appScreenshots", "sessions.forkWorkspaceMaxBytes", "sessions.wedgedTurnMinutes",
|
|
370
|
+
"sessions.sync", "sessions.worktreeSync", "sessions.standbyNodeId", "sessions.resume", "sessions.autoAttachToolImages", "sessions.appScreenshots", "sessions.previewBadge", "sessions.forkWorkspaceMaxBytes", "sessions.wedgedTurnMinutes",
|
|
368
371
|
"github.issuePrompt", "automation.checks", "automation.checkTimeoutMinutes",
|
|
369
372
|
]);
|
|
370
373
|
if (!allowed.has(dotted) && !/^agents\.[a-z][a-z0-9-]{1,47}$/.test(dotted) && !/^environment\.[A-Z][A-Z0-9_]+$/.test(dotted))
|
package/dist/server.js
CHANGED
|
@@ -1920,8 +1920,32 @@ const captureReviewerNote = async (entry, mark) => {
|
|
|
1920
1920
|
const ref = attachmentStore.put(Buffer.from(image.data, "base64"), { name: "Reviewer note (approximate).png", mimeType: "image/png", kind: "image" });
|
|
1921
1921
|
return { hash: ref.hash, size: ref.size, width: image.width, height: image.height };
|
|
1922
1922
|
};
|
|
1923
|
-
|
|
1924
|
-
|
|
1923
|
+
/** "Made with Bivy" under shared previews. Shown unless this machine turned it
|
|
1924
|
+
* off (sessions.previewBadge / BIVY_PREVIEW_BADGE) and — when it belongs to an
|
|
1925
|
+
* account — the control plane allows that account to hide it. Without a
|
|
1926
|
+
* control plane the machine's owner decides. Allowance cached for 10 minutes;
|
|
1927
|
+
* if it can't be checked, the badge shows. */
|
|
1928
|
+
let previewBadgeAllowance;
|
|
1929
|
+
async function previewBadgeShown() {
|
|
1930
|
+
const env = process.env.BIVY_PREVIEW_BADGE;
|
|
1931
|
+
const off = env === "0" || env === "false" || (env === undefined && readSettings().previewBadge === false);
|
|
1932
|
+
if (!off)
|
|
1933
|
+
return true;
|
|
1934
|
+
if (!sessionAdvertiseTarget)
|
|
1935
|
+
return false;
|
|
1936
|
+
if (!previewBadgeAllowance || Date.now() - previewBadgeAllowance.at > 10 * 60_000) {
|
|
1937
|
+
try {
|
|
1938
|
+
const body = await delegatedRunRequest("/node/preview-badge");
|
|
1939
|
+
previewBadgeAllowance = { at: Date.now(), hideAllowed: body.hideAllowed === true };
|
|
1940
|
+
}
|
|
1941
|
+
catch {
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
return !previewBadgeAllowance.hideAllowed;
|
|
1946
|
+
}
|
|
1947
|
+
const appGateway = process.env.BIVY_APPS_ORIGIN ? new AppGateway(appRegistry, process.env.BIVY_APPS_ORIGIN, appReturnOrigins, appSignIn, captureReviewerNote, previewBadgeShown) : undefined;
|
|
1948
|
+
const remotePreview = new RemotePreview(appRegistry, appReturnOrigins, appSignIn, captureReviewerNote, previewBadgeShown);
|
|
1925
1949
|
// Desktop app views: one private display per view, started on first open.
|
|
1926
1950
|
const appDisplays = createDisplayHost(appDir);
|
|
1927
1951
|
/** Review cards: screenshots become encrypted attachments (fetched by hash
|
package/package.json
CHANGED