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
package/dist/cua-actor-lab.js
CHANGED
|
@@ -31,8 +31,9 @@ import { CHROMIUM_EVIDENCE_HYGIENE_FLAGS, chromiumEvidenceProfilePreferencesJson
|
|
|
31
31
|
import { DEFAULT_OPENAI_CU_MODEL } from "./openai-responses-cu.js";
|
|
32
32
|
import { createDesktopSandbox, loadE2BDesktopModule } from "./e2b-desktop-launch.js";
|
|
33
33
|
import { probeUrl, readDetachedLog, runDetachedStep, startDetachedProcess } from "./e2b-detached.js";
|
|
34
|
-
import { DEFAULT_SANDBOX_CATCH_PORT, collectCommsThread, deployCommsCatch } from "./comms-sandbox-catch.js";
|
|
34
|
+
import { DEFAULT_SANDBOX_CATCH_PORT, collectCommsThread, deployCommsCatch, refreshInboxSurface, writeInboxSurface } from "./comms-sandbox-catch.js";
|
|
35
35
|
import { FakeInbox } from "./comms-fake-inbox.js";
|
|
36
|
+
import { buildOriginMap } from "./comms-inbox.js";
|
|
36
37
|
import { DEFAULT_DEVICE_PRESET, isDevicePresetName, resolveDevicePreset } from "./device-presets.js";
|
|
37
38
|
import { cuaLaneValidationReason, isHttpUrl, isLoopbackUrl, MAX_CUA_LANES, subjectStateInvalidReason } from "./lab-config.js";
|
|
38
39
|
import { mapWithConcurrency } from "./concurrency.js";
|
|
@@ -110,6 +111,25 @@ export function composeLaneInstructions(args) {
|
|
|
110
111
|
}
|
|
111
112
|
};
|
|
112
113
|
}
|
|
114
|
+
/** Runtime-inject the persona inbox instruction into a lane's prompt (#297 slice B). The inbox URL is a
|
|
115
|
+
* runtime loopback/getHost address (not secret), so — mirroring the lobby-code runtime injection — this
|
|
116
|
+
* augments only the instructions the model receives; the authored prompt + its digest are unchanged.
|
|
117
|
+
* Returns a new spec (never mutates). Shared by the CUA + concurrent shared-world routes. */
|
|
118
|
+
export function withInboxMission(spec, inboxUrl) {
|
|
119
|
+
return {
|
|
120
|
+
...spec,
|
|
121
|
+
instructions: `${spec.instructions}\n\nEmail inbox: when the app tells you it has emailed you (a verification link, confirmation code, or magic link), open ${inboxUrl} in the browser to read that email and follow its link or enter its code. All email the app sends you arrives there.`
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** True when a lane has a declared comms recipient WITH an address, so the drain can actually match the
|
|
125
|
+
* mail the persona will be told to read. Gates the inbox instruction to lanes that can receive mail —
|
|
126
|
+
* a lane told to check an inbox it can never receive into would just stall. */
|
|
127
|
+
export function laneHasInboxRecipient(commsEmail, laneId) {
|
|
128
|
+
return (commsEmail.recipients ?? []).some((recipient) => recipient.lane === laneId && recipient.address !== undefined);
|
|
129
|
+
}
|
|
130
|
+
/** Mid-run inbox-surface render cadence (ms). Coarse enough that the per-tick `cat` + file writes stay
|
|
131
|
+
* cheap; fine enough that a verification email is visible seconds after the app sends it. */
|
|
132
|
+
const INBOX_SURFACE_CADENCE_MS = 2500;
|
|
113
133
|
/**
|
|
114
134
|
* The narrowest browser WINDOW Chrome/Chromium will render on the E2B desktop. Chrome refuses to
|
|
115
135
|
* make its window narrower than this (~500 CSS px observed: a 414-wide X screen produced a 500-wide
|
|
@@ -1002,6 +1022,26 @@ export async function runCuaLane(spec, deps) {
|
|
|
1002
1022
|
const commsEnv = commsEmail && commsPort !== undefined
|
|
1003
1023
|
? { [commsEmail.injectEnv]: `http://127.0.0.1:${commsPort}` }
|
|
1004
1024
|
: {};
|
|
1025
|
+
// Persona inbox SURFACE (#297 slice B): the loopback URL the persona opens to read captured mail; the
|
|
1026
|
+
// origin-rewrite map (identity on this same-sandbox route, but covers localhost/0.0.0.0 alias skew + an
|
|
1027
|
+
// operator-declared linkOrigin); and a disposable background loop that renders the surface DURING the
|
|
1028
|
+
// session so the inbox is live when the persona checks. The surface uses its OWN FakeInbox + cursor,
|
|
1029
|
+
// independent of the teardown evidence drain (two readers of the append-only NDJSON — no double-count).
|
|
1030
|
+
const commsInboxUrl = commsEmail && commsPort !== undefined ? `http://127.0.0.1:${commsPort}/inbox` : undefined;
|
|
1031
|
+
const commsOriginMap = commsEmail
|
|
1032
|
+
? buildOriginMap({
|
|
1033
|
+
...(config.subject.serve?.url === undefined ? {} : { internalServeUrl: config.subject.serve.url }),
|
|
1034
|
+
reachableBaseUrl: targetUrl,
|
|
1035
|
+
...(commsEmail.linkOrigin === undefined ? {} : { linkOrigin: commsEmail.linkOrigin })
|
|
1036
|
+
})
|
|
1037
|
+
: [];
|
|
1038
|
+
let surfaceChannel;
|
|
1039
|
+
const surfaceInboxes = [];
|
|
1040
|
+
let surfaceCursor = 0;
|
|
1041
|
+
let surfaceDisposed = false;
|
|
1042
|
+
let releaseSurface = () => { };
|
|
1043
|
+
const surfaceDispose = new Promise((resolve) => { releaseSurface = resolve; });
|
|
1044
|
+
let surfaceLoop;
|
|
1005
1045
|
const warnings = [];
|
|
1006
1046
|
const screenshots = [];
|
|
1007
1047
|
const writeScreenshot = makeLaneWriteScreenshot(deps.artifactRoot, spec, screenshots);
|
|
@@ -1091,6 +1131,51 @@ export async function runCuaLane(spec, deps) {
|
|
|
1091
1131
|
if (!deployedComms.ready) {
|
|
1092
1132
|
throw new Error(`comms email catch did not become ready on 127.0.0.1:${commsPort} in the subject sandbox`);
|
|
1093
1133
|
}
|
|
1134
|
+
// Stand up the live inbox surface: provision the declared-recipient inboxes on a dedicated surface
|
|
1135
|
+
// channel, then start a background loop that drains-and-renders on a cadence so the persona sees new
|
|
1136
|
+
// mail mid-session. Disposed at the TOP of the finally, before the teardown evidence drain.
|
|
1137
|
+
surfaceChannel = new FakeInbox();
|
|
1138
|
+
for (const recipient of commsEmail.recipients ?? []) {
|
|
1139
|
+
if (recipient.address !== undefined)
|
|
1140
|
+
surfaceInboxes.push(await surfaceChannel.provisionAddress(recipient.lane, recipient.address));
|
|
1141
|
+
}
|
|
1142
|
+
// Write the EMPTY inbox once up front so the persona's /inbox always resolves to the "No messages
|
|
1143
|
+
// yet." page — never a bare 404 — the instant it navigates there, even before any mail arrives OR if
|
|
1144
|
+
// the app sends to an address no declared recipient matches (the loop only re-renders on new mail).
|
|
1145
|
+
await writeInboxSurface(desktop, deployedComms.surfaceDir, [], { originMap: commsOriginMap, requestTimeoutMs: deps.requestTimeoutMs });
|
|
1146
|
+
const deployedRef = deployedComms;
|
|
1147
|
+
const surfaceChannelRef = surfaceChannel;
|
|
1148
|
+
surfaceLoop = (async () => {
|
|
1149
|
+
// Render-first (so even a short session gets a populated inbox), then refresh on a cadence. The
|
|
1150
|
+
// cadence uses a REAL timer, NOT the injected instant clock: this loop is unbounded, so an instant
|
|
1151
|
+
// sleep would busy-spin and starve the session's own timers. The wait is interruptible by
|
|
1152
|
+
// surfaceDispose (and the timer cleared) so teardown never blocks for a full cadence.
|
|
1153
|
+
for (;;) {
|
|
1154
|
+
try {
|
|
1155
|
+
const refreshed = await refreshInboxSurface({
|
|
1156
|
+
desktop,
|
|
1157
|
+
deployed: deployedRef,
|
|
1158
|
+
channel: surfaceChannelRef,
|
|
1159
|
+
inboxes: surfaceInboxes,
|
|
1160
|
+
cursor: surfaceCursor,
|
|
1161
|
+
originMap: commsOriginMap,
|
|
1162
|
+
requestTimeoutMs: deps.requestTimeoutMs
|
|
1163
|
+
});
|
|
1164
|
+
surfaceCursor = refreshed.cursor;
|
|
1165
|
+
}
|
|
1166
|
+
catch {
|
|
1167
|
+
// Never throw into the render loop; the teardown drain + by-id teardown must still run.
|
|
1168
|
+
}
|
|
1169
|
+
if (surfaceDisposed)
|
|
1170
|
+
break;
|
|
1171
|
+
await new Promise((resolve) => {
|
|
1172
|
+
const timer = setTimeout(resolve, INBOX_SURFACE_CADENCE_MS);
|
|
1173
|
+
void surfaceDispose.then(() => { clearTimeout(timer); resolve(); });
|
|
1174
|
+
});
|
|
1175
|
+
if (surfaceDisposed)
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
})();
|
|
1094
1179
|
}
|
|
1095
1180
|
// Per-lane geometry assertion (fail-closed) — the device claim is verified in-sandbox.
|
|
1096
1181
|
const screenGeometry = await inspectDesktopScreenGeometry({
|
|
@@ -1210,7 +1295,11 @@ export async function runCuaLane(spec, deps) {
|
|
|
1210
1295
|
const capModelId = config.actors[0]?.model ?? DEFAULT_OPENAI_CU_MODEL;
|
|
1211
1296
|
const maxUsd = config.execution?.caps?.maxUsd;
|
|
1212
1297
|
const sessionOptions = {
|
|
1213
|
-
|
|
1298
|
+
// Tell the persona where its inbox is — but only when comms is live AND this lane has a declared
|
|
1299
|
+
// recipient it can actually receive mail into (else it would stall on an inbox that stays empty).
|
|
1300
|
+
instructions: commsEmail && commsInboxUrl && deployedComms?.ready && laneHasInboxRecipient(commsEmail, spec.laneId)
|
|
1301
|
+
? withInboxMission(spec, commsInboxUrl).instructions
|
|
1302
|
+
: spec.instructions,
|
|
1214
1303
|
persona: spec.persona,
|
|
1215
1304
|
timeoutMs: deps.timeoutMs,
|
|
1216
1305
|
openai: {
|
|
@@ -1252,6 +1341,13 @@ export async function runCuaLane(spec, deps) {
|
|
|
1252
1341
|
sessionError = redactText(deps.scrubKnownValues(toErrorMessage(error)));
|
|
1253
1342
|
}
|
|
1254
1343
|
finally {
|
|
1344
|
+
// Stop the mid-run inbox-surface loop FIRST — before the teardown evidence drain below — so the two
|
|
1345
|
+
// `cat`s never overlap and the final surface state is deterministic. A surface failure can never
|
|
1346
|
+
// block teardown (the loop body is fully try/caught and this await is on its already-caught promise).
|
|
1347
|
+
surfaceDisposed = true;
|
|
1348
|
+
releaseSurface();
|
|
1349
|
+
if (surfaceLoop)
|
|
1350
|
+
await surfaceLoop.catch(() => undefined);
|
|
1255
1351
|
if (!provisioned) {
|
|
1256
1352
|
signal(false);
|
|
1257
1353
|
}
|