humanish 0.26.0 → 0.28.0
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/comms-inbox.d.ts +60 -0
- package/dist/comms-inbox.js +290 -0
- package/dist/comms-inbox.js.map +1 -0
- package/dist/comms-sandbox-catch.d.ts +39 -1
- package/dist/comms-sandbox-catch.js +80 -4
- package/dist/comms-sandbox-catch.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +10 -1
- package/dist/cua-actor-lab.js +98 -2
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lab-config.d.ts +6 -0
- package/dist/lab-config.js +12 -0
- package/dist/lab-config.js.map +1 -1
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +1 -1
- package/docs/ramp/README.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { CommsMessage } from "./comms-types.js";
|
|
2
|
+
/** [fromOrigin, toOrigin] pairs: rewrite a link/href whose origin is the app's in-sandbox origin into a
|
|
3
|
+
* lane-reachable origin, so the persona's browser can actually follow the verify link when it clicks. */
|
|
4
|
+
export type OriginMap = Array<[string, string]>;
|
|
5
|
+
export interface InboxRenderOptions {
|
|
6
|
+
originMap?: OriginMap;
|
|
7
|
+
}
|
|
8
|
+
/** One served file: a route path (no leading slash) + its body + content type. The host writes each to
|
|
9
|
+
* `<servedDir>/<path>`; the catch serves `<servedDir>/<pathname>` verbatim. */
|
|
10
|
+
export interface InboxSurfaceFile {
|
|
11
|
+
path: string;
|
|
12
|
+
contentType: "text/html; charset=utf-8" | "application/json; charset=utf-8";
|
|
13
|
+
body: string;
|
|
14
|
+
}
|
|
15
|
+
/** Rewrite a URL's origin per the map (origins are exact strings the harness minted). Leaves
|
|
16
|
+
* non-matching URLs — including sibling origins sharing a prefix — untouched. */
|
|
17
|
+
export declare function rewriteOrigin(url: string, originMap?: OriginMap): string;
|
|
18
|
+
/**
|
|
19
|
+
* Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes ITS OWN
|
|
20
|
+
* origin into the verify links it emails (usually its loopback serve origin); the persona reaches the
|
|
21
|
+
* app at a possibly-different origin — the same loopback on the CUA route (identity, a no-op), the
|
|
22
|
+
* harness-minted getHost URL on the shared-world route (where the rewrite is REQUIRED). Emits the serve
|
|
23
|
+
* origin plus its loopback aliases at the serve port (127.0.0.1 / localhost / 0.0.0.0) so an app that
|
|
24
|
+
* stamps `localhost` still rewrites, and — because the harness cannot infer an absolute PUBLIC base URL
|
|
25
|
+
* an app was configured with — an operator-declared `linkOrigin` escape hatch, matched first. Origins
|
|
26
|
+
* only (no trailing slash, no path), so replaceOriginBoundary matches on a URL boundary.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildOriginMap(args: {
|
|
29
|
+
internalServeUrl?: string | undefined;
|
|
30
|
+
reachableBaseUrl?: string | undefined;
|
|
31
|
+
linkOrigin?: string | undefined;
|
|
32
|
+
}): OriginMap;
|
|
33
|
+
/** Best-guess primary call-to-action: the first verify/confirm-looking link, else the first link. */
|
|
34
|
+
export declare function pickVerifyUrl(links: string[]): string | undefined;
|
|
35
|
+
/** The surface CSP — the browser-enforced, load-bearing protection against the app-authored email running
|
|
36
|
+
* script or hijacking navigation on the surface page. `script-src 'none'` blocks inline handlers,
|
|
37
|
+
* `javascript:` URLs, and any injected <script>; `object-src`/`frame-src 'none'` block plugins/frames;
|
|
38
|
+
* `base-uri 'none'` blocks <base> reroot. Images/styles stay permissive so the real email still renders.
|
|
39
|
+
* Set BOTH as a page() meta (covers direct render use) and as a catch response header (covers serving). */
|
|
40
|
+
export declare const INBOX_SURFACE_CSP = "default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'none'; frame-src 'none'; img-src * data:; style-src 'unsafe-inline'; font-src * data:";
|
|
41
|
+
/** The inbox LIST page (semantic table; each row links to its message). */
|
|
42
|
+
export declare function renderInboxList(messages: CommsMessage[]): string;
|
|
43
|
+
/** One message, DEFAULT view: the app's real captured email in a minimal high-contrast shell. */
|
|
44
|
+
export declare function renderInboxMessage(message: CommsMessage, options?: InboxRenderOptions): string;
|
|
45
|
+
/** One message, SYNTHESIZED view: a guaranteed-legible reading pane (big verify button + big OTP). The
|
|
46
|
+
* reliability fallback when the app's real email is a vision minefield; opt-in, never the default. */
|
|
47
|
+
export declare function renderInboxMessageSynth(message: CommsMessage, options?: InboxRenderOptions): string;
|
|
48
|
+
/** JSON projection of one message (the programmatic/CLI actor surface + the reliability backstop). Raw
|
|
49
|
+
* runtime values (served in-sandbox only); links/verifyUrl origin-rewritten so a consumer can follow. */
|
|
50
|
+
export declare function inboxMessageJson(message: CommsMessage, options?: InboxRenderOptions): Record<string, unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* Build every served file for the inbox surface from the normalized messages. The host writes each to
|
|
53
|
+
* `<servedDir>/<path>`; the in-sandbox catch serves `<servedDir>/<pathname>`, falling back to
|
|
54
|
+
* `<pathname>/index` (so a URL like `/inbox` maps to the `inbox/index` file while `/inbox/{id}` stays a
|
|
55
|
+
* directory) — standard web-server index semantics that avoid a file-vs-directory path collision. The
|
|
56
|
+
* URLs the persona actually sees stay clean (`/inbox`, `/inbox/{id}`, `/inbox/{id}/synth`). Content type
|
|
57
|
+
* is inferred from the `/api/` prefix. Message ids are `comms-NNNN` (never `index`/`latest`), so leaf
|
|
58
|
+
* filenames never collide with an id.
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildInboxSurface(messages: CommsMessage[], options?: InboxRenderOptions): InboxSurfaceFile[];
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// The persona-facing INBOX SURFACE (#297, slice B): a minimal, dependency-free, high-contrast set of
|
|
2
|
+
// pages a computer-use persona opens to read a captured verification email and click its link — the
|
|
3
|
+
// last step of the off-app comms funnel (redirect → capture → drain → evidence → SURFACE).
|
|
4
|
+
//
|
|
5
|
+
// Design (research-backed, 2026-08-04): agent/CUA benchmarks hand agents either NO ui or a DELIBERATELY
|
|
6
|
+
// simplified inbox (MiniWoB email-inbox); nobody hands a vision agent a chrome-heavy webmail client.
|
|
7
|
+
// Anthropic computer-use guidance: high contrast, single column, big hit-targets, minimal state. So the
|
|
8
|
+
// surface is minimal SEMANTIC HTML (native <a>/<table>, black-on-white, large targets) — NOT a component
|
|
9
|
+
// UI (shadcn/Base UI break both the no-build ethos AND vision-agent reliability). We render the app's
|
|
10
|
+
// REAL captured email by default (faithful to the app-under-test — an honest signal if its email is a
|
|
11
|
+
// legibility mess), with a synthesized clean view one path away, plus a JSON twin for programmatic
|
|
12
|
+
// actors. Route shape mirrors Mailpit/Inbucket (list, message, `latest` alias, JSON with pre-extracted
|
|
13
|
+
// links/otp).
|
|
14
|
+
//
|
|
15
|
+
// This module is PURE + typed + tested; it consumes the already-normalized CommsMessage[] the tested
|
|
16
|
+
// drain/route path produces (links + codes already extracted), and emits the route→content files the
|
|
17
|
+
// in-sandbox catch serves statically. Raw content here is runtime-only (rendered in-sandbox, served to
|
|
18
|
+
// the in-sandbox browser); it is never persisted — the persisted evidence stays digest-only.
|
|
19
|
+
const VERIFY_HINT = /verify|confirm|activate|validate|magic|token|account|sign[\s-]?up/i;
|
|
20
|
+
/** Replace `from` with `to` everywhere it ends on an ORIGIN BOUNDARY — end-of-string or one of the URL
|
|
21
|
+
* delimiters `/ ? # " ' <space> > \`. A bare prefix replace would mangle a sibling origin that shares
|
|
22
|
+
* `from` as a numeric prefix (`:30000` vs `:3000`) or a suffix-domain (`:3000.evil`); the boundary
|
|
23
|
+
* check prevents that. Works both for a single URL and inside a blob of email HTML. */
|
|
24
|
+
function replaceOriginBoundary(text, from, to) {
|
|
25
|
+
if (!from)
|
|
26
|
+
return text;
|
|
27
|
+
let out = "";
|
|
28
|
+
let i = 0;
|
|
29
|
+
for (;;) {
|
|
30
|
+
const idx = text.indexOf(from, i);
|
|
31
|
+
if (idx === -1)
|
|
32
|
+
return out + text.slice(i);
|
|
33
|
+
const after = text.charAt(idx + from.length); // "" past end-of-string
|
|
34
|
+
const atBoundary = after === "" || "/?#\"' >\\".includes(after);
|
|
35
|
+
out += text.slice(i, idx) + (atBoundary ? to : from);
|
|
36
|
+
i = idx + from.length;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Rewrite a URL's origin per the map (origins are exact strings the harness minted). Leaves
|
|
40
|
+
* non-matching URLs — including sibling origins sharing a prefix — untouched. */
|
|
41
|
+
export function rewriteOrigin(url, originMap = []) {
|
|
42
|
+
let out = url;
|
|
43
|
+
for (const [from, to] of originMap)
|
|
44
|
+
out = replaceOriginBoundary(out, from, to);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function safeOrigin(url) {
|
|
48
|
+
if (!url)
|
|
49
|
+
return undefined;
|
|
50
|
+
try {
|
|
51
|
+
return new URL(url).origin;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function safePort(url) {
|
|
58
|
+
if (!url)
|
|
59
|
+
return undefined;
|
|
60
|
+
try {
|
|
61
|
+
return new URL(url).port || undefined;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build the [internalOrigin → reachableOrigin] rewrite rows for a run. The app-under-test bakes ITS OWN
|
|
69
|
+
* origin into the verify links it emails (usually its loopback serve origin); the persona reaches the
|
|
70
|
+
* app at a possibly-different origin — the same loopback on the CUA route (identity, a no-op), the
|
|
71
|
+
* harness-minted getHost URL on the shared-world route (where the rewrite is REQUIRED). Emits the serve
|
|
72
|
+
* origin plus its loopback aliases at the serve port (127.0.0.1 / localhost / 0.0.0.0) so an app that
|
|
73
|
+
* stamps `localhost` still rewrites, and — because the harness cannot infer an absolute PUBLIC base URL
|
|
74
|
+
* an app was configured with — an operator-declared `linkOrigin` escape hatch, matched first. Origins
|
|
75
|
+
* only (no trailing slash, no path), so replaceOriginBoundary matches on a URL boundary.
|
|
76
|
+
*/
|
|
77
|
+
export function buildOriginMap(args) {
|
|
78
|
+
const to = safeOrigin(args.reachableBaseUrl);
|
|
79
|
+
if (to === undefined)
|
|
80
|
+
return [];
|
|
81
|
+
const froms = [];
|
|
82
|
+
const declared = safeOrigin(args.linkOrigin);
|
|
83
|
+
if (declared !== undefined)
|
|
84
|
+
froms.push(declared); // operator-declared origin wins (matched first)
|
|
85
|
+
const serve = safeOrigin(args.internalServeUrl);
|
|
86
|
+
if (serve !== undefined) {
|
|
87
|
+
froms.push(serve);
|
|
88
|
+
const port = safePort(args.internalServeUrl);
|
|
89
|
+
if (port !== undefined)
|
|
90
|
+
for (const host of ["127.0.0.1", "localhost", "0.0.0.0"])
|
|
91
|
+
froms.push(`http://${host}:${port}`);
|
|
92
|
+
}
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
const map = [];
|
|
95
|
+
for (const from of froms) {
|
|
96
|
+
if (from !== to && !seen.has(from)) {
|
|
97
|
+
seen.add(from);
|
|
98
|
+
map.push([from, to]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return map;
|
|
102
|
+
}
|
|
103
|
+
/** Best-guess primary call-to-action: the first verify/confirm-looking link, else the first link. */
|
|
104
|
+
export function pickVerifyUrl(links) {
|
|
105
|
+
return links.find((link) => VERIFY_HINT.test(link)) ?? links[0];
|
|
106
|
+
}
|
|
107
|
+
function esc(value) {
|
|
108
|
+
return value
|
|
109
|
+
.replace(/&/g, "&")
|
|
110
|
+
.replace(/</g, "<")
|
|
111
|
+
.replace(/>/g, ">")
|
|
112
|
+
.replace(/"/g, """);
|
|
113
|
+
}
|
|
114
|
+
/** Defense-in-depth stripping before rendering the app's real email HTML. The LOAD-BEARING protection is
|
|
115
|
+
* the page CSP (`script-src 'none'`, set both as a page() meta and as a catch response header), which is
|
|
116
|
+
* browser-enforced and neuters inline handlers, `javascript:` URLs, and injected <script> regardless of
|
|
117
|
+
* what this misses. This strip additionally removes elements CSP does not cover (a redirecting
|
|
118
|
+
* <meta http-equiv=refresh>, a nav-rerooting <base>) and the obvious active tags, so the two layers
|
|
119
|
+
* together stop the app-authored (untrusted) email from executing script or redirecting the surface. */
|
|
120
|
+
function neutralizeEmailHtml(html, originMap) {
|
|
121
|
+
let out = html
|
|
122
|
+
// paired dangerous/framing elements + their content
|
|
123
|
+
.replace(/<(script|style|iframe|object|embed|svg|math|template)\b[\s\S]*?<\/\1\s*>/gi, "")
|
|
124
|
+
// stray or void dangerous tags, incl. unclosed openers and redirect/reroot tags
|
|
125
|
+
.replace(/<\/?(script|style|iframe|object|embed|svg|math|base|meta|link|form)\b[^>]*>/gi, "")
|
|
126
|
+
// inline event handlers, whether preceded by whitespace OR a "/" attribute separator
|
|
127
|
+
.replace(/[\s/]on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, " ")
|
|
128
|
+
// javascript:/vbscript: in nav/resource attrs — quoted OR unquoted (data: is left for inline images)
|
|
129
|
+
.replace(/(href|src|xlink:href|formaction|action)\s*=\s*(?:"(?:javascript|vbscript):[^"]*"|'(?:javascript|vbscript):[^']*'|(?:javascript|vbscript):[^\s>]*)/gi, '$1="#"');
|
|
130
|
+
// Rewrite the app's origin inside the (now-neutralized) HTML so its own links resolve to a reachable host.
|
|
131
|
+
for (const [from, to] of originMap)
|
|
132
|
+
out = replaceOriginBoundary(out, from, to);
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
const PAGE_CSS = "body{font:16px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#111;background:#fff;margin:0}" +
|
|
136
|
+
"a{color:#0645ad}" +
|
|
137
|
+
".bar{padding:10px 20px;background:#f4f4f4;border-bottom:1px solid #ccc;font-size:14px}" +
|
|
138
|
+
".hdr{padding:16px 20px;border-bottom:3px solid #111}" +
|
|
139
|
+
".hdr div{margin:2px 0}.hdr b{display:inline-block;min-width:72px;color:#444;font-weight:600}" +
|
|
140
|
+
".body{padding:20px;max-width:820px}" +
|
|
141
|
+
"table{border-collapse:collapse;width:100%;max-width:900px}" +
|
|
142
|
+
"th,td{text-align:left;padding:12px 16px;border-bottom:1px solid #ddd;font-size:16px}" +
|
|
143
|
+
"th{border-bottom:2px solid #111}" +
|
|
144
|
+
"tr:hover{background:#f7f7f7}" +
|
|
145
|
+
".verify{display:inline-block;padding:16px 32px;background:#111;color:#fff;text-decoration:none;font-size:18px;font-weight:600;border-radius:6px}" +
|
|
146
|
+
".otp{font:32px/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:8px;background:#f0f0f0;padding:12px 18px;display:inline-block;border-radius:6px}" +
|
|
147
|
+
".muted{color:#666;font-size:13px}";
|
|
148
|
+
/** The surface CSP — the browser-enforced, load-bearing protection against the app-authored email running
|
|
149
|
+
* script or hijacking navigation on the surface page. `script-src 'none'` blocks inline handlers,
|
|
150
|
+
* `javascript:` URLs, and any injected <script>; `object-src`/`frame-src 'none'` block plugins/frames;
|
|
151
|
+
* `base-uri 'none'` blocks <base> reroot. Images/styles stay permissive so the real email still renders.
|
|
152
|
+
* Set BOTH as a page() meta (covers direct render use) and as a catch response header (covers serving). */
|
|
153
|
+
export const INBOX_SURFACE_CSP = "default-src 'self'; script-src 'none'; object-src 'none'; base-uri 'none'; frame-src 'none'; img-src * data:; style-src 'unsafe-inline'; font-src * data:";
|
|
154
|
+
function page(title, inner) {
|
|
155
|
+
return ("<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">" +
|
|
156
|
+
"<meta http-equiv=\"Content-Security-Policy\" content=\"" + INBOX_SURFACE_CSP + "\">" +
|
|
157
|
+
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" +
|
|
158
|
+
"<title>" + esc(title) + "</title><style>" + PAGE_CSS + "</style></head><body>" +
|
|
159
|
+
inner +
|
|
160
|
+
"</body></html>");
|
|
161
|
+
}
|
|
162
|
+
function fmtTime(ms) {
|
|
163
|
+
// Deterministic, dependency-free UTC stamp (avoids Date locale/timezone drift in tests + sandbox).
|
|
164
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
165
|
+
return "";
|
|
166
|
+
const d = new Date(ms);
|
|
167
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
168
|
+
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC`;
|
|
169
|
+
}
|
|
170
|
+
function toValues(message) {
|
|
171
|
+
return message.to.map((address) => address.value);
|
|
172
|
+
}
|
|
173
|
+
/** The inbox LIST page (semantic table; each row links to its message). */
|
|
174
|
+
export function renderInboxList(messages) {
|
|
175
|
+
const rows = messages
|
|
176
|
+
.map((message) => "<tr><td><a href=\"/inbox/" + esc(message.id) + "\">" + esc(message.subject ?? "(no subject)") + "</a></td>" +
|
|
177
|
+
"<td>" + esc(message.from) + "</td>" +
|
|
178
|
+
"<td>" + esc(toValues(message).join(", ")) + "</td>" +
|
|
179
|
+
"<td class=\"muted\">" + esc(fmtTime(message.deliveredAt)) + "</td></tr>")
|
|
180
|
+
.join("");
|
|
181
|
+
const body = "<div class=\"bar\">Inbox — " + messages.length + " message" + (messages.length === 1 ? "" : "s") + "</div>" +
|
|
182
|
+
"<main class=\"body\"><table><thead><tr><th>Subject</th><th>From</th><th>To</th><th>Received</th></tr></thead>" +
|
|
183
|
+
"<tbody>" + (rows || "<tr><td colspan=\"4\" class=\"muted\">No messages yet.</td></tr>") + "</tbody></table></main>";
|
|
184
|
+
return page("Inbox", body);
|
|
185
|
+
}
|
|
186
|
+
/** One message, DEFAULT view: the app's real captured email in a minimal high-contrast shell. */
|
|
187
|
+
export function renderInboxMessage(message, options = {}) {
|
|
188
|
+
const originMap = options.originMap ?? [];
|
|
189
|
+
const realHtml = message.body ? neutralizeEmailHtml(message.body, originMap) : "";
|
|
190
|
+
const fallback = realHtml.trim().length > 0 ? realHtml : "<p class=\"muted\">(this message had no HTML body)</p>";
|
|
191
|
+
const body = "<div class=\"bar\"><a href=\"/inbox\">← Inbox</a> · <a href=\"/inbox/" + esc(message.id) + "/synth\">plain view</a></div>" +
|
|
192
|
+
"<div class=\"hdr\"><div><b>To</b> " + esc(toValues(message).join(", ")) + "</div>" +
|
|
193
|
+
"<div><b>From</b> " + esc(message.from) + "</div>" +
|
|
194
|
+
"<div><b>Subject</b> " + esc(message.subject ?? "(no subject)") + "</div></div>" +
|
|
195
|
+
"<main class=\"body\">" + fallback + "</main>";
|
|
196
|
+
return page(message.subject ?? "Message", body);
|
|
197
|
+
}
|
|
198
|
+
/** One message, SYNTHESIZED view: a guaranteed-legible reading pane (big verify button + big OTP). The
|
|
199
|
+
* reliability fallback when the app's real email is a vision minefield; opt-in, never the default. */
|
|
200
|
+
export function renderInboxMessageSynth(message, options = {}) {
|
|
201
|
+
const originMap = options.originMap ?? [];
|
|
202
|
+
const verify = pickVerifyUrl(message.links);
|
|
203
|
+
const verifyRewritten = verify ? rewriteOrigin(verify, originMap) : undefined;
|
|
204
|
+
const otp = message.codes[0];
|
|
205
|
+
const parts = [
|
|
206
|
+
"<div class=\"bar\"><a href=\"/inbox\">← Inbox</a> · <a href=\"/inbox/" + esc(message.id) + "\">real email</a></div>",
|
|
207
|
+
"<div class=\"hdr\"><div><b>From</b> " + esc(message.from) + "</div>" +
|
|
208
|
+
"<div><b>To</b> " + esc(toValues(message).join(", ")) + "</div>" +
|
|
209
|
+
"<div><b>Subject</b> " + esc(message.subject ?? "(no subject)") + "</div></div>",
|
|
210
|
+
"<main class=\"body\" style=\"text-align:center\">"
|
|
211
|
+
];
|
|
212
|
+
if (verifyRewritten) {
|
|
213
|
+
parts.push("<p style=\"margin:32px 0\"><a class=\"verify\" href=\"" + esc(verifyRewritten) + "\">Verify email</a></p>");
|
|
214
|
+
}
|
|
215
|
+
if (otp) {
|
|
216
|
+
parts.push("<p>Your code:</p><p><span class=\"otp\">" + esc(otp) + "</span></p>");
|
|
217
|
+
}
|
|
218
|
+
if (!verifyRewritten && !otp) {
|
|
219
|
+
parts.push("<p class=\"muted\">No verification link or code was found in this message. Open the <a href=\"/inbox/" + esc(message.id) + "\">real email</a>.</p>");
|
|
220
|
+
}
|
|
221
|
+
parts.push("</main>");
|
|
222
|
+
return page(message.subject ?? "Message", parts.join(""));
|
|
223
|
+
}
|
|
224
|
+
/** JSON projection of one message (the programmatic/CLI actor surface + the reliability backstop). Raw
|
|
225
|
+
* runtime values (served in-sandbox only); links/verifyUrl origin-rewritten so a consumer can follow. */
|
|
226
|
+
export function inboxMessageJson(message, options = {}) {
|
|
227
|
+
const originMap = options.originMap ?? [];
|
|
228
|
+
const links = message.links.map((link) => rewriteOrigin(link, originMap));
|
|
229
|
+
const verify = pickVerifyUrl(links);
|
|
230
|
+
return {
|
|
231
|
+
id: message.id,
|
|
232
|
+
from: message.from,
|
|
233
|
+
to: toValues(message),
|
|
234
|
+
subject: message.subject ?? null,
|
|
235
|
+
receivedAt: message.deliveredAt,
|
|
236
|
+
text: message.body,
|
|
237
|
+
links,
|
|
238
|
+
...(verify === undefined ? {} : { verifyUrl: verify }),
|
|
239
|
+
...(message.codes[0] === undefined ? {} : { otp: message.codes[0] })
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function inboxListJson(messages, options = {}) {
|
|
243
|
+
const originMap = options.originMap ?? [];
|
|
244
|
+
return messages.map((message) => {
|
|
245
|
+
const verify = pickVerifyUrl(message.links.map((link) => rewriteOrigin(link, originMap)));
|
|
246
|
+
return {
|
|
247
|
+
id: message.id,
|
|
248
|
+
from: message.from,
|
|
249
|
+
to: toValues(message),
|
|
250
|
+
subject: message.subject ?? null,
|
|
251
|
+
receivedAt: message.deliveredAt,
|
|
252
|
+
...(verify === undefined ? {} : { verifyUrl: verify }),
|
|
253
|
+
...(message.codes[0] === undefined ? {} : { otp: message.codes[0] })
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Build every served file for the inbox surface from the normalized messages. The host writes each to
|
|
259
|
+
* `<servedDir>/<path>`; the in-sandbox catch serves `<servedDir>/<pathname>`, falling back to
|
|
260
|
+
* `<pathname>/index` (so a URL like `/inbox` maps to the `inbox/index` file while `/inbox/{id}` stays a
|
|
261
|
+
* directory) — standard web-server index semantics that avoid a file-vs-directory path collision. The
|
|
262
|
+
* URLs the persona actually sees stay clean (`/inbox`, `/inbox/{id}`, `/inbox/{id}/synth`). Content type
|
|
263
|
+
* is inferred from the `/api/` prefix. Message ids are `comms-NNNN` (never `index`/`latest`), so leaf
|
|
264
|
+
* filenames never collide with an id.
|
|
265
|
+
*/
|
|
266
|
+
export function buildInboxSurface(messages, options = {}) {
|
|
267
|
+
const html = (path, body) => ({ path, contentType: "text/html; charset=utf-8", body });
|
|
268
|
+
const json = (path, value) => ({
|
|
269
|
+
path,
|
|
270
|
+
contentType: "application/json; charset=utf-8",
|
|
271
|
+
body: JSON.stringify(value, null, 2)
|
|
272
|
+
});
|
|
273
|
+
const files = [
|
|
274
|
+
html("inbox/index", renderInboxList(messages)),
|
|
275
|
+
json("api/inbox/index", inboxListJson(messages, options))
|
|
276
|
+
];
|
|
277
|
+
const latest = messages[messages.length - 1];
|
|
278
|
+
for (const message of messages) {
|
|
279
|
+
files.push(html("inbox/" + message.id + "/index", renderInboxMessage(message, options)));
|
|
280
|
+
files.push(html("inbox/" + message.id + "/synth", renderInboxMessageSynth(message, options)));
|
|
281
|
+
files.push(json("api/inbox/" + message.id, inboxMessageJson(message, options)));
|
|
282
|
+
}
|
|
283
|
+
if (latest) {
|
|
284
|
+
files.push(html("inbox/latest/index", renderInboxMessage(latest, options)));
|
|
285
|
+
files.push(html("inbox/latest/synth", renderInboxMessageSynth(latest, options)));
|
|
286
|
+
files.push(json("api/inbox/latest", inboxMessageJson(latest, options)));
|
|
287
|
+
}
|
|
288
|
+
return files;
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=comms-inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-inbox.js","sourceRoot":"","sources":["../src/comms-inbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { CommsAddress, CommsChannel } from "./comms-types.js";
|
|
1
|
+
import type { CommsAddress, CommsChannel, CommsMessage } from "./comms-types.js";
|
|
2
2
|
import { type CommsThreadArtifact } from "./comms-evidence.js";
|
|
3
|
+
import { type InboxRenderOptions } from "./comms-inbox.js";
|
|
3
4
|
import { type EmailSendProfile } from "./comms-email-catch.js";
|
|
4
5
|
import { type DetachedTimers } from "./e2b-detached.js";
|
|
5
6
|
import type { E2BDesktopSandbox } from "./e2b-desktop-launch.js";
|
|
@@ -32,6 +33,9 @@ export interface DeployedCommsCatch {
|
|
|
32
33
|
/** Inject THIS as the app's email-API base URL (e.g. RESEND_API_URL) — the sandbox's own loopback. */
|
|
33
34
|
baseUrl: string;
|
|
34
35
|
deliveriesPath: string;
|
|
36
|
+
/** In-sandbox dir the HOST renders the persona-facing inbox-surface files into (via writeInboxSurface);
|
|
37
|
+
* the catch serves them at /inbox and /api/inbox. */
|
|
38
|
+
surfaceDir: string;
|
|
35
39
|
/** Whether the catch's /health returned OUR service marker within the readiness budget. Callers MUST
|
|
36
40
|
* treat `ready === false` as fatal (do not inject baseUrl into a dead catch — the app's sends would
|
|
37
41
|
* silently fail with nothing captured). */
|
|
@@ -92,3 +96,37 @@ export declare function collectCommsThread(args: {
|
|
|
92
96
|
profiles?: EmailSendProfile[];
|
|
93
97
|
requestTimeoutMs?: number;
|
|
94
98
|
}): Promise<CommsThreadCollection>;
|
|
99
|
+
/**
|
|
100
|
+
* Render the persona-facing inbox surface (host-side, typed — see comms-inbox.ts) and write the files
|
|
101
|
+
* into the sandbox's served dir, so the catch serves a LIVE inbox the persona opens and clicks. Creates
|
|
102
|
+
* the nested route dirs first; overwrites idempotently, so call it whenever the message set changes
|
|
103
|
+
* (e.g. after a mid-run drain). Returns the number of files written. Raw content is written INTO the
|
|
104
|
+
* sandbox only (runtime-only, served to the in-sandbox browser); nothing here persists to the bundle.
|
|
105
|
+
*/
|
|
106
|
+
export declare function writeInboxSurface(desktop: E2BDesktopSandbox, surfaceDir: string, messages: CommsMessage[], options?: InboxRenderOptions & {
|
|
107
|
+
requestTimeoutMs?: number;
|
|
108
|
+
}): Promise<number>;
|
|
109
|
+
/**
|
|
110
|
+
* One mid-run inbox-surface refresh cycle: drain the catch INCREMENTALLY from `cursor` into the
|
|
111
|
+
* persistent surface `channel`, poll the provisioned `inboxes`, and (re)render the surface so the
|
|
112
|
+
* persona sees new mail while the session is live. Returns the advanced cursor + whether it rendered.
|
|
113
|
+
*
|
|
114
|
+
* MUST use a channel dedicated to the surface — NOT the teardown evidence channel: the teardown
|
|
115
|
+
* collectCommsThread drains the same append-only NDJSON from cursor 0 into its OWN fresh channel, so
|
|
116
|
+
* feeding both an incremental and a cursor-0 drain into one channel would route every send twice
|
|
117
|
+
* (FakeInbox mints fresh ids each time, so dedup-by-id can't catch it) and double-count the evidence.
|
|
118
|
+
* Two channels + two cursors are independent readers of the same file — no evidence is lost or altered.
|
|
119
|
+
* Skips the (N-file) render when no new sends arrived this tick, so idle ticks are cheap.
|
|
120
|
+
*/
|
|
121
|
+
export declare function refreshInboxSurface(args: {
|
|
122
|
+
desktop: E2BDesktopSandbox;
|
|
123
|
+
deployed: Pick<DeployedCommsCatch, "deliveriesPath" | "surfaceDir">;
|
|
124
|
+
channel: CommsChannel;
|
|
125
|
+
inboxes: CommsAddress[];
|
|
126
|
+
cursor: number;
|
|
127
|
+
originMap?: InboxRenderOptions["originMap"];
|
|
128
|
+
requestTimeoutMs?: number;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
cursor: number;
|
|
131
|
+
rendered: boolean;
|
|
132
|
+
}>;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// parse them and route into the CommsChannel. A FIXED loopback port is chosen up front so the app's
|
|
8
8
|
// injected base-URL env (`http://127.0.0.1:<port>`) is known before the sandbox is created.
|
|
9
9
|
import { buildCommsThreadArtifact } from "./comms-evidence.js";
|
|
10
|
+
import { buildInboxSurface } from "./comms-inbox.js";
|
|
10
11
|
import { DEFAULT_EMAIL_PROFILES } from "./comms-email-catch.js";
|
|
11
12
|
import { startDetachedProcess } from "./e2b-detached.js";
|
|
12
13
|
/** The default in-sandbox loopback port for the catch. Fixed (not ephemeral) so the injected base-URL
|
|
@@ -27,15 +28,31 @@ function shq(value) {
|
|
|
27
28
|
*/
|
|
28
29
|
export const SANDBOX_CATCH_SCRIPT = [
|
|
29
30
|
'import { createServer } from "node:http";',
|
|
30
|
-
'import { appendFileSync, mkdirSync } from "node:fs";',
|
|
31
|
+
'import { appendFileSync, mkdirSync, readFileSync } from "node:fs";',
|
|
31
32
|
'import { dirname } from "node:path";',
|
|
32
33
|
'const port = Number(process.argv[2] || 8025);',
|
|
33
34
|
'const outFile = process.argv[3] || "/tmp/humanish-comms/deliveries.ndjson";',
|
|
35
|
+
// argv[4] is the dir the HOST renders the inbox-surface files into (host-side typed rendering; this
|
|
36
|
+
// server only serves them statically), defaulting to a `surface` sibling of the deliveries file.
|
|
37
|
+
'const servedDir = process.argv[4] || (dirname(outFile) + "/surface");',
|
|
34
38
|
'try { mkdirSync(dirname(outFile), { recursive: true }); } catch {}',
|
|
35
39
|
'const MAX = 5 * 1024 * 1024;',
|
|
36
40
|
'createServer((req, res) => {',
|
|
37
41
|
' const path = (req.url || "/").split("?")[0];',
|
|
38
42
|
' if (req.method === "GET" && (path === "/" || path === "/health")) { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, service: "humanish-comms-catch" })); return; }',
|
|
43
|
+
' if (req.method === "GET" && (path === "/inbox" || path.indexOf("/inbox/") === 0 || path === "/api/inbox" || path.indexOf("/api/inbox/") === 0)) {',
|
|
44
|
+
' let rel; try { rel = decodeURIComponent(path); } catch { res.writeHead(400); res.end(); return; }',
|
|
45
|
+
' if (rel.indexOf("..") !== -1 || rel.indexOf("\\0") !== -1) { res.writeHead(400); res.end(); return; }',
|
|
46
|
+
' let data = null;',
|
|
47
|
+
' try { data = readFileSync(servedDir + rel); } catch { try { data = readFileSync(servedDir + rel + "/index"); } catch { data = null; } }',
|
|
48
|
+
' if (data === null) { res.writeHead(404, { "content-type": "text/html; charset=utf-8" }); res.end("<p>message not found</p>"); return; }',
|
|
49
|
+
' const isApi = rel.indexOf("/api/") === 0;',
|
|
50
|
+
' const headers = { "content-type": isApi ? "application/json; charset=utf-8" : "text/html; charset=utf-8" };',
|
|
51
|
+
// The load-bearing XSS protection: a browser-enforced CSP forbidding all script on the surface page,
|
|
52
|
+
// so the app-authored (untrusted) email HTML we render cannot run script or reroot navigation.
|
|
53
|
+
' if (!isApi) { headers["content-security-policy"] = "default-src \'self\'; script-src \'none\'; object-src \'none\'; base-uri \'none\'; frame-src \'none\'; img-src * data:; style-src \'unsafe-inline\'; font-src * data:"; }',
|
|
54
|
+
' res.writeHead(200, headers); res.end(data); return;',
|
|
55
|
+
' }',
|
|
39
56
|
' if (req.method !== "POST") { res.writeHead(404, { "content-type": "application/json" }); res.end(JSON.stringify({ error: "not found" })); return; }',
|
|
40
57
|
' let size = 0; const chunks = [];',
|
|
41
58
|
' req.on("data", (c) => { size += c.length; if (size > MAX) { req.destroy(); } else { chunks.push(c); } });',
|
|
@@ -83,11 +100,12 @@ export async function deployCommsCatch(desktop, options = {}) {
|
|
|
83
100
|
const requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
|
|
84
101
|
const scriptPath = `${dir}/catch.mjs`;
|
|
85
102
|
const deliveriesPath = `${dir}/deliveries.ndjson`;
|
|
86
|
-
|
|
103
|
+
const surfaceDir = `${dir}/surface`;
|
|
104
|
+
await desktop.commands.run(`mkdir -p ${shq(dir)} ${shq(surfaceDir)}`, { requestTimeoutMs });
|
|
87
105
|
await desktop.files.write(scriptPath, SANDBOX_CATCH_SCRIPT);
|
|
88
106
|
await startDetachedProcess(desktop, {
|
|
89
107
|
name,
|
|
90
|
-
command: `node ${shq(scriptPath)} ${port} ${shq(deliveriesPath)}`,
|
|
108
|
+
command: `node ${shq(scriptPath)} ${port} ${shq(deliveriesPath)} ${shq(surfaceDir)}`,
|
|
91
109
|
requestTimeoutMs
|
|
92
110
|
});
|
|
93
111
|
const ready = await catchHealthy(desktop, port, {
|
|
@@ -95,7 +113,7 @@ export async function deployCommsCatch(desktop, options = {}) {
|
|
|
95
113
|
requestTimeoutMs,
|
|
96
114
|
...(options.timers ?? {})
|
|
97
115
|
});
|
|
98
|
-
return { port, baseUrl: `http://127.0.0.1:${port}`, deliveriesPath, ready };
|
|
116
|
+
return { port, baseUrl: `http://127.0.0.1:${port}`, deliveriesPath, surfaceDir, ready };
|
|
99
117
|
}
|
|
100
118
|
/**
|
|
101
119
|
* Drain new captured sends from the in-sandbox NDJSON since `cursor` (a line count). Returns the fresh
|
|
@@ -186,4 +204,62 @@ export async function collectCommsThread(args) {
|
|
|
186
204
|
messages.sort((a, b) => a.deliveredAt - b.deliveredAt || a.id.localeCompare(b.id));
|
|
187
205
|
return { artifact: buildCommsThreadArtifact(messages), captured: sends.length, matched: messages.length };
|
|
188
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Render the persona-facing inbox surface (host-side, typed — see comms-inbox.ts) and write the files
|
|
209
|
+
* into the sandbox's served dir, so the catch serves a LIVE inbox the persona opens and clicks. Creates
|
|
210
|
+
* the nested route dirs first; overwrites idempotently, so call it whenever the message set changes
|
|
211
|
+
* (e.g. after a mid-run drain). Returns the number of files written. Raw content is written INTO the
|
|
212
|
+
* sandbox only (runtime-only, served to the in-sandbox browser); nothing here persists to the bundle.
|
|
213
|
+
*/
|
|
214
|
+
export async function writeInboxSurface(desktop, surfaceDir, messages, options = {}) {
|
|
215
|
+
const files = buildInboxSurface(messages, options);
|
|
216
|
+
// Create the union of parent dirs (inbox/<id>/synth etc.) in one mkdir before writing.
|
|
217
|
+
const dirs = new Set([surfaceDir]);
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
const slash = file.path.lastIndexOf("/");
|
|
220
|
+
if (slash > 0)
|
|
221
|
+
dirs.add(`${surfaceDir}/${file.path.slice(0, slash)}`);
|
|
222
|
+
}
|
|
223
|
+
await desktop.commands.run(`mkdir -p ${[...dirs].map(shq).join(" ")}`, { requestTimeoutMs: options.requestTimeoutMs ?? 30_000 });
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
await desktop.files.write(`${surfaceDir}/${file.path}`, file.body);
|
|
226
|
+
}
|
|
227
|
+
return files.length;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* One mid-run inbox-surface refresh cycle: drain the catch INCREMENTALLY from `cursor` into the
|
|
231
|
+
* persistent surface `channel`, poll the provisioned `inboxes`, and (re)render the surface so the
|
|
232
|
+
* persona sees new mail while the session is live. Returns the advanced cursor + whether it rendered.
|
|
233
|
+
*
|
|
234
|
+
* MUST use a channel dedicated to the surface — NOT the teardown evidence channel: the teardown
|
|
235
|
+
* collectCommsThread drains the same append-only NDJSON from cursor 0 into its OWN fresh channel, so
|
|
236
|
+
* feeding both an incremental and a cursor-0 drain into one channel would route every send twice
|
|
237
|
+
* (FakeInbox mints fresh ids each time, so dedup-by-id can't catch it) and double-count the evidence.
|
|
238
|
+
* Two channels + two cursors are independent readers of the same file — no evidence is lost or altered.
|
|
239
|
+
* Skips the (N-file) render when no new sends arrived this tick, so idle ticks are cheap.
|
|
240
|
+
*/
|
|
241
|
+
export async function refreshInboxSurface(args) {
|
|
242
|
+
const { sends, cursor } = await drainCommsCatch(args.desktop, args.deployed, args.cursor, args.requestTimeoutMs);
|
|
243
|
+
if (sends.length === 0)
|
|
244
|
+
return { cursor, rendered: false };
|
|
245
|
+
await routeCapturedSends(sends, args.channel);
|
|
246
|
+
const seen = new Set();
|
|
247
|
+
const messages = [];
|
|
248
|
+
for (const inbox of args.inboxes) {
|
|
249
|
+
for (const message of await args.channel.poll(inbox, 0)) {
|
|
250
|
+
if (seen.has(message.id))
|
|
251
|
+
continue;
|
|
252
|
+
seen.add(message.id);
|
|
253
|
+
messages.push(message);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (messages.length === 0)
|
|
257
|
+
return { cursor, rendered: false };
|
|
258
|
+
messages.sort((a, b) => a.deliveredAt - b.deliveredAt || a.id.localeCompare(b.id));
|
|
259
|
+
await writeInboxSurface(args.desktop, args.deployed.surfaceDir, messages, {
|
|
260
|
+
...(args.originMap === undefined ? {} : { originMap: args.originMap }),
|
|
261
|
+
...(args.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: args.requestTimeoutMs })
|
|
262
|
+
});
|
|
263
|
+
return { cursor, rendered: true };
|
|
264
|
+
}
|
|
189
265
|
//# sourceMappingURL=comms-sandbox-catch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comms-sandbox-catch.js","sourceRoot":"","sources":["../src/comms-sandbox-catch.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,wGAAwG;AACxG,uGAAuG;AACvG,qGAAqG;AACrG,qGAAqG;AACrG,qGAAqG;AACrG,oGAAoG;AACpG,4FAA4F;AAG5F,OAAO,EAAE,wBAAwB,EAA4B,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAyB,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,oBAAoB,EAAuB,MAAM,mBAAmB,CAAC;AAG9E;;8EAE8E;AAC9E,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAC/C,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,iDAAiD;AACjD,SAAS,GAAG,CAAC,KAAa;IACxB,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,2CAA2C;IAC3C,
|
|
1
|
+
{"version":3,"file":"comms-sandbox-catch.js","sourceRoot":"","sources":["../src/comms-sandbox-catch.ts"],"names":[],"mappings":"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"}
|
package/dist/cua-actor-lab.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { CuaExecutor, CuaLoopResult, CuaProvider } from "./computer-use.js"
|
|
|
6
6
|
import { type E2BDesktopModule, type E2BDesktopSandbox } from "./e2b-desktop-launch.js";
|
|
7
7
|
import { type DetachedTimers } from "./e2b-detached.js";
|
|
8
8
|
import { type DevicePreset } from "./device-presets.js";
|
|
9
|
-
import { type LabActorLane, type LabConfig, type LabDesktopBrowser, type LabSubjectServe, type LabSubjectState } from "./lab-config.js";
|
|
9
|
+
import { type LabActorLane, type LabCommsEmail, type LabConfig, type LabDesktopBrowser, type LabSubjectServe, type LabSubjectState } from "./lab-config.js";
|
|
10
10
|
import { renderObserver, type ObserverResult } from "./observer.js";
|
|
11
11
|
import { type PreparedOutputDirectory } from "./selected-output-paths.js";
|
|
12
12
|
import { type LocalTreeArchive } from "./source-archive.js";
|
|
@@ -376,6 +376,15 @@ export declare function composeLaneInstructions(args: {
|
|
|
376
376
|
instructions: string;
|
|
377
377
|
persona: ActorPersonaRef;
|
|
378
378
|
};
|
|
379
|
+
/** Runtime-inject the persona inbox instruction into a lane's prompt (#297 slice B). The inbox URL is a
|
|
380
|
+
* runtime loopback/getHost address (not secret), so — mirroring the lobby-code runtime injection — this
|
|
381
|
+
* augments only the instructions the model receives; the authored prompt + its digest are unchanged.
|
|
382
|
+
* Returns a new spec (never mutates). Shared by the CUA + concurrent shared-world routes. */
|
|
383
|
+
export declare function withInboxMission(spec: CuaLaneSpec, inboxUrl: string): CuaLaneSpec;
|
|
384
|
+
/** True when a lane has a declared comms recipient WITH an address, so the drain can actually match the
|
|
385
|
+
* mail the persona will be told to read. Gates the inbox instruction to lanes that can receive mail —
|
|
386
|
+
* a lane told to check an inbox it can never receive into would just stall. */
|
|
387
|
+
export declare function laneHasInboxRecipient(commsEmail: LabCommsEmail, laneId: string): boolean;
|
|
379
388
|
/**
|
|
380
389
|
* The narrowest browser WINDOW Chrome/Chromium will render on the E2B desktop. Chrome refuses to
|
|
381
390
|
* make its window narrower than this (~500 CSS px observed: a 414-wide X screen produced a 500-wide
|