humanish 0.76.0 → 0.78.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/README.md +6 -4
- package/dist/chrome-cdp-probe.d.ts +4 -2
- package/dist/chrome-cdp-probe.js +238 -110
- package/dist/chrome-cdp-probe.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +22 -1
- package/dist/cua-actor-lab.js +94 -8
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-desktop-launch.d.ts +33 -1
- package/dist/e2b-desktop-launch.js +49 -4
- package/dist/e2b-desktop-launch.js.map +1 -1
- package/dist/openai-responses-cu.d.ts +14 -0
- package/dist/openai-responses-cu.js +55 -2
- package/dist/openai-responses-cu.js.map +1 -1
- package/dist/run.d.ts +27 -0
- package/dist/run.js +37 -1
- package/dist/run.js.map +1 -1
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +38 -5
- package/docs/ramp/README.md +1 -1
- package/package.json +2 -2
package/dist/cua-actor-lab.js
CHANGED
|
@@ -34,7 +34,7 @@ import { DEFAULT_OPENAI_CU_MODEL } from "./openai-responses-cu.js";
|
|
|
34
34
|
import { createLocalAgentProvider, detectLocalAgents } from "./local-agent-cli.js";
|
|
35
35
|
import { startAppServerSession } from "./local-agent-appserver.js";
|
|
36
36
|
import { startClaudeSession } from "./local-agent-claude-session.js";
|
|
37
|
-
import { createDesktopSandbox, loadE2BDesktopModule } from "./e2b-desktop-launch.js";
|
|
37
|
+
import { createDesktopSandbox, withOneRetryOnTransientE2BError, loadE2BDesktopModule } from "./e2b-desktop-launch.js";
|
|
38
38
|
import { probeUrl, readDetachedLog, runDetachedStep, startDetachedProcess } from "./e2b-detached.js";
|
|
39
39
|
import { DEFAULT_SANDBOX_CATCH_PORT, collectCommsThread, collectExternalCommsThread, deployCommsCatch, externalCatchHealthy, externalInboxUrl, refreshInboxSurface, writeInboxSurface } from "./comms-sandbox-catch.js";
|
|
40
40
|
import { FakeInbox } from "./comms-fake-inbox.js";
|
|
@@ -885,8 +885,19 @@ export function desktopBrowserFamily(value) {
|
|
|
885
885
|
* The observer still degrades to `{}` for the loop; the callback is how a lane says out loud that
|
|
886
886
|
* url/text criteria are not being measured, instead of letting the funnel report 0/N (#514).
|
|
887
887
|
*/
|
|
888
|
-
export function makeChromeBrowserStateObserver(desktop, requestTimeoutMs, endpoint, targetId, onUnavailable
|
|
888
|
+
export function makeChromeBrowserStateObserver(desktop, requestTimeoutMs, endpoint, targetId, onUnavailable,
|
|
889
|
+
/**
|
|
890
|
+
* Mobile emulation on later tabs (#623): the holder attaches to every page target Chrome opens
|
|
891
|
+
* after the launch page, so a tab the participant opens later should lay out at the phone width
|
|
892
|
+
* too. The first observation on each new target reads that page's OWN report; a target that
|
|
893
|
+
* reports the requested width is recorded through `onCovered`, and one that does not (or cannot
|
|
894
|
+
* be read) fires `onDrift` once, so a phone-labelled lane that spent part of its session at
|
|
895
|
+
* desktop layout says so with the number the page gave.
|
|
896
|
+
*/
|
|
897
|
+
drift) {
|
|
889
898
|
let reported = false;
|
|
899
|
+
let drifted = false;
|
|
900
|
+
const checkedTargets = new Set(drift === undefined ? [] : [drift.emulatedTargetId]);
|
|
890
901
|
const unavailable = (reason) => {
|
|
891
902
|
if (!reported) {
|
|
892
903
|
reported = true;
|
|
@@ -894,6 +905,29 @@ export function makeChromeBrowserStateObserver(desktop, requestTimeoutMs, endpoi
|
|
|
894
905
|
}
|
|
895
906
|
return {};
|
|
896
907
|
};
|
|
908
|
+
const checkLaterTarget = async (newTargetId) => {
|
|
909
|
+
if (drift === undefined || checkedTargets.has(newTargetId))
|
|
910
|
+
return;
|
|
911
|
+
checkedTargets.add(newTargetId);
|
|
912
|
+
const read = await desktop.commands.run(chromeCdpProbeCommand({ ...endpoint, targetId: newTargetId, prefer: "pinned", mode: "fidelity" }), { requestTimeoutMs, timeoutMs: 5_000 });
|
|
913
|
+
const fidelity = read.exitCode !== undefined && read.exitCode !== 0 ? undefined : parseChromeCdpProbeOutput(read.stdout).fidelity;
|
|
914
|
+
if (fidelity !== undefined && fidelity.innerWidth === drift.expectedWidth) {
|
|
915
|
+
drift.onCovered?.(newTargetId, { innerWidth: fidelity.innerWidth, devicePixelRatio: fidelity.devicePixelRatio, maxTouchPoints: fidelity.maxTouchPoints });
|
|
916
|
+
if (drift.expectTouch === true && fidelity.maxTouchPoints === 0 && !drifted) {
|
|
917
|
+
// The viewport followed; touch did not (yet): the holder reloads a later tab once after its
|
|
918
|
+
// first navigation commits, and this observation may have landed before that reload.
|
|
919
|
+
drifted = true;
|
|
920
|
+
drift.onDrift(`a later page target reports the ${fidelity.innerWidth} px viewport but navigator.maxTouchPoints 0 on its first observation; touch reaches a document only when it loads under the override`);
|
|
921
|
+
}
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (drifted)
|
|
925
|
+
return;
|
|
926
|
+
drifted = true;
|
|
927
|
+
drift.onDrift(fidelity === undefined
|
|
928
|
+
? "the participant drove a page target other than the emulated launch tab and that page's own read-back could not be taken; whether it laid out at the phone width is not known"
|
|
929
|
+
: `the participant drove a page target other than the emulated launch tab and that page reports a ${fidelity.innerWidth} px viewport where ${drift.expectedWidth} px was requested (DPR ${fidelity.devicePixelRatio}); the mobile user agent and touch events are browser-wide, the viewport override was not re-applied to it`);
|
|
930
|
+
};
|
|
897
931
|
return async () => {
|
|
898
932
|
const result = await desktop.commands.run(chromeCdpProbeCommand({ ...endpoint, ...(targetId === undefined ? {} : { targetId }), prefer: "active", mode: "state" }), { requestTimeoutMs, timeoutMs: 5_000 });
|
|
899
933
|
if (result.exitCode !== undefined && result.exitCode !== 0) {
|
|
@@ -902,6 +936,8 @@ export function makeChromeBrowserStateObserver(desktop, requestTimeoutMs, endpoi
|
|
|
902
936
|
const parsed = parseChromeCdpProbeOutput(result.stdout);
|
|
903
937
|
if (parsed.unavailable !== undefined)
|
|
904
938
|
return unavailable(parsed.unavailable);
|
|
939
|
+
if (parsed.targetId !== undefined)
|
|
940
|
+
await checkLaterTarget(parsed.targetId);
|
|
905
941
|
return {
|
|
906
942
|
...(parsed.url === undefined ? {} : { url: parsed.url }),
|
|
907
943
|
...(parsed.title === undefined ? {} : { title: parsed.title }),
|
|
@@ -994,9 +1030,10 @@ export async function applyMobileEmulation(desktop, requestTimeoutMs, endpoint,
|
|
|
994
1030
|
touch: request.touch,
|
|
995
1031
|
userAgent: request.userAgent
|
|
996
1032
|
};
|
|
1033
|
+
const emulatedTargetId = applied.targetId ?? fidelityRead.targetId;
|
|
997
1034
|
if (fidelityRead.fidelity === undefined) {
|
|
998
1035
|
warnings.push(`Mobile emulation was applied but the page's own report could not be read (${fidelityRead.unavailable ?? "no fidelity read"}); desktopGeometry.fidelity carries the request without a resolved block.`);
|
|
999
|
-
return { fidelity: { tier: "mobile-emulated", requested, applied: applied.applied ?? [] }, warnings };
|
|
1036
|
+
return { fidelity: { tier: "mobile-emulated", requested, applied: applied.applied ?? [] }, warnings, holderName, ...(emulatedTargetId === undefined ? {} : { targetId: emulatedTargetId }) };
|
|
1000
1037
|
}
|
|
1001
1038
|
const resolved = { ...fidelityRead.fidelity, source: "cdp" };
|
|
1002
1039
|
if (resolved.innerWidth !== request.width) {
|
|
@@ -1011,7 +1048,7 @@ export async function applyMobileEmulation(desktop, requestTimeoutMs, endpoint,
|
|
|
1011
1048
|
if (!resolved.userAgent.includes("Mobile") && !resolved.userAgent.includes("Android") && !resolved.userAgent.includes("iPhone")) {
|
|
1012
1049
|
warnings.push("Mobile emulation requested a mobile user agent; the page reports a desktop one.");
|
|
1013
1050
|
}
|
|
1014
|
-
return { fidelity: { tier: "mobile-emulated", requested, applied: applied.applied ?? [], resolved }, warnings };
|
|
1051
|
+
return { fidelity: { tier: "mobile-emulated", requested, applied: applied.applied ?? [], resolved }, warnings, holderName, ...(emulatedTargetId === undefined ? {} : { targetId: emulatedTargetId }) };
|
|
1015
1052
|
}
|
|
1016
1053
|
function isMeasuredRect(value) {
|
|
1017
1054
|
if (!value || typeof value !== "object")
|
|
@@ -1291,12 +1328,16 @@ function stripResolvedArcSegments(text) {
|
|
|
1291
1328
|
})
|
|
1292
1329
|
.join(" ");
|
|
1293
1330
|
}
|
|
1331
|
+
// Negations that DESCRIBE a defect rather than deny one. Kept out of every clause drop below.
|
|
1332
|
+
const DEFECT_SHAPED_NEGATION = /\bno\s+(?:visible\s+)?focus\b|\bno\s+(?:keyboard|screen.?reader)[- ]?(?:access|path|route|way|alternative|equivalent)|\bnot\s+(?:keyboard|screen.?reader)[- ]?accessible\b|\bno\s+(?:effect|feedback|response)\b|\b(?:did|does)\s+nothing\b|\bnothing\s+happened\b/;
|
|
1294
1333
|
function stripNegatedNonBlockerPhrases(text) {
|
|
1295
1334
|
return text
|
|
1296
1335
|
// FIRST, before the narrower rules eat the "no blockers" and leave "encountered ... error"
|
|
1297
1336
|
// behind: "I encountered no blockers or unclear error output." refused a clean passing run on
|
|
1298
1337
|
// 2026-09-01. A verb of encounter followed by "no" negates the whole clause, so drop the clause.
|
|
1299
|
-
|
|
1338
|
+
// ... unless the clause names a DEFECT: "the delete control had no visible focus" is a
|
|
1339
|
+
// finding, and the verb it happens to use must not decide whether it counts (#622).
|
|
1340
|
+
.replace(/\b(?:encountered|hit|saw|found|met|had|got|ran into)\s+no\s+[^.!?\n]*/g, (clause) => DEFECT_SHAPED_NEGATION.test(clause) ? clause : " ")
|
|
1300
1341
|
.replace(/\bno\s+(?:real\s+|remaining\s+|actual\s+)?(?:blocker|blockers|blocking issue|blocking issues|error|errors|failure|failures)\s+(?:was\s+|were\s+)?(?:encountered|observed|found|hit|seen|reported|detected)\b/g, "")
|
|
1301
1342
|
.replace(/\bwithout\s+(?:a\s+|any\s+)?(?:real\s+|remaining\s+|actual\s+)?(?:blocker|blockers|blocking issue|blocking issues|error|errors|failure|failures)\b/g, "")
|
|
1302
1343
|
.replace(/\bnot\s+(?:blocked|a blocker|an error|failed)\b/g, "")
|
|
@@ -1460,6 +1501,8 @@ export async function runCuaLane(spec, deps) {
|
|
|
1460
1501
|
let browserLaunched = false;
|
|
1461
1502
|
let initialBrowserGeometry;
|
|
1462
1503
|
let appliedFidelity;
|
|
1504
|
+
let emulatedTargetId;
|
|
1505
|
+
let emulationHolderName;
|
|
1463
1506
|
let browserWindowId;
|
|
1464
1507
|
let browserTargetId;
|
|
1465
1508
|
const declaredScreen = declaredScreenForRender(spec.devicePreset, spec.deviceName, spec.resolution);
|
|
@@ -1512,7 +1555,17 @@ export async function runCuaLane(spec, deps) {
|
|
|
1512
1555
|
resolution: spec.resolution,
|
|
1513
1556
|
dpi: 96,
|
|
1514
1557
|
lifecycle: { onTimeout: "kill" }
|
|
1515
|
-
}, config.execution?.desktop?.template
|
|
1558
|
+
}, config.execution?.desktop?.template, {
|
|
1559
|
+
// One retry on a transient provider error (a sandbox whose envd was not routable yet, an
|
|
1560
|
+
// API reply without a body). The failed attempt may have allocated a sandbox this process
|
|
1561
|
+
// never learned the id of; its own timeoutMs is what reclaims it, so the warning says so.
|
|
1562
|
+
onRetry: (reason) => {
|
|
1563
|
+
const named = redactText(deps.scrubKnownValues(reason));
|
|
1564
|
+
warnings.push(`Sandbox create for lane ${spec.laneId} retried once after a transient provider error (${named}); ` +
|
|
1565
|
+
`a sandbox the first attempt may have allocated is not known to this run and expires on the provider's ${Math.round(deps.perLaneSandboxMs / 60_000)}-minute timeout.`);
|
|
1566
|
+
onSubjectPhase({ at: new Date(deps.now()).toISOString(), type: "cua-lab.sandbox.create.retry", message: `sandbox create retried once (${named})` });
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1516
1569
|
sandboxId = desktop.sandboxId;
|
|
1517
1570
|
// #358 salvage: journal the id to disk before any work — an interrupted run reclaims by
|
|
1518
1571
|
// exact recorded id (`humanish reclaim`), never by enumerating the account.
|
|
@@ -1687,6 +1740,8 @@ export async function runCuaLane(spec, deps) {
|
|
|
1687
1740
|
userAgent: fidelityRequest.userAgent ?? DEFAULT_MOBILE_USER_AGENT
|
|
1688
1741
|
});
|
|
1689
1742
|
appliedFidelity = applied.fidelity;
|
|
1743
|
+
emulatedTargetId = applied.targetId;
|
|
1744
|
+
emulationHolderName = applied.holderName;
|
|
1690
1745
|
warnings.push(...applied.warnings);
|
|
1691
1746
|
}
|
|
1692
1747
|
}
|
|
@@ -1828,7 +1883,26 @@ export async function runCuaLane(spec, deps) {
|
|
|
1828
1883
|
(reason) => {
|
|
1829
1884
|
warnings.push(`Browser-state observer unavailable for lane ${spec.laneId} (${redactText(deps.scrubKnownValues(reason))}); ` +
|
|
1830
1885
|
"urlIncludes/urlPathEquals/textIncludes stop conditions and task criteria are NOT being measured this session.");
|
|
1831
|
-
}
|
|
1886
|
+
}, emulatedTargetId === undefined
|
|
1887
|
+
? undefined
|
|
1888
|
+
: {
|
|
1889
|
+
emulatedTargetId,
|
|
1890
|
+
expectedWidth: spec.devicePreset.width,
|
|
1891
|
+
expectTouch: appliedFidelity?.requested.touch === true,
|
|
1892
|
+
onDrift: (reason) => {
|
|
1893
|
+
warnings.push(`Mobile emulation drift on lane ${spec.laneId}: ${reason} (#623).`);
|
|
1894
|
+
},
|
|
1895
|
+
onCovered: (coveredTargetId, read) => {
|
|
1896
|
+
// A later tab the page itself reported at the phone width: evidence that
|
|
1897
|
+
// the emulation followed the participant (#623), kept on the bundle.
|
|
1898
|
+
if (appliedFidelity === undefined)
|
|
1899
|
+
return;
|
|
1900
|
+
appliedFidelity = {
|
|
1901
|
+
...appliedFidelity,
|
|
1902
|
+
laterTargets: [...(appliedFidelity.laterTargets ?? []), { targetId: coveredTargetId, ...read }]
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
})
|
|
1832
1906
|
}
|
|
1833
1907
|
}
|
|
1834
1908
|
: {}),
|
|
@@ -1909,6 +1983,15 @@ export async function runCuaLane(spec, deps) {
|
|
|
1909
1983
|
: initialBrowserGeometry ?? finalGeometry;
|
|
1910
1984
|
const geometryWarnings = [...new Set(chosenGeometry.warnings.map((warning) => deps.scrubKnownValues(warning)))];
|
|
1911
1985
|
warnings.push(...geometryWarnings);
|
|
1986
|
+
// The emulation holder's own log, after its announce line: which later targets it
|
|
1987
|
+
// attached to, what it sent, and any reply that came back as an error (#623). Read while
|
|
1988
|
+
// the sandbox is alive; the first live proof had no way to say what the holder did.
|
|
1989
|
+
if (appliedFidelity !== undefined && emulationHolderName !== undefined) {
|
|
1990
|
+
const holderLog = await readDetachedLog(desktop, emulationHolderName, deps.requestTimeoutMs).catch(() => "");
|
|
1991
|
+
const lines = holderLog.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("{")).slice(1, 51);
|
|
1992
|
+
if (lines.length > 0)
|
|
1993
|
+
appliedFidelity = { ...appliedFidelity, holderLog: lines.map((line) => deps.scrubKnownValues(line)) };
|
|
1994
|
+
}
|
|
1912
1995
|
desktopGeometry = {
|
|
1913
1996
|
screen: desktopGeometry.screen,
|
|
1914
1997
|
...(chosenGeometry.browserWindow === undefined ? {} : { browserWindow: chosenGeometry.browserWindow }),
|
|
@@ -3555,9 +3638,12 @@ export async function provisionLocalTreeSubject(desktop, args) {
|
|
|
3555
3638
|
const uploadStartedAt = now();
|
|
3556
3639
|
emitPhaseStarted(args.onPhase, now, "upload", "uploading packed local-tree archive");
|
|
3557
3640
|
try {
|
|
3558
|
-
await desktop.files.write(LOCAL_TREE_REMOTE_ARCHIVE_PATH, args.archiveBuffer, {
|
|
3641
|
+
await withOneRetryOnTransientE2BError(() => desktop.files.write(LOCAL_TREE_REMOTE_ARCHIVE_PATH, args.archiveBuffer, {
|
|
3559
3642
|
requestTimeoutMs: args.requestTimeoutMs,
|
|
3560
3643
|
useOctetStream: true
|
|
3644
|
+
}), {
|
|
3645
|
+
onRetry: (reason) => emitPhaseStarted(args.onPhase, now, "upload-retry", `local-tree archive upload retried once (${tailOf(args.scrub(reason))})`),
|
|
3646
|
+
...(args.sleep === undefined ? {} : { sleep: args.sleep })
|
|
3561
3647
|
});
|
|
3562
3648
|
}
|
|
3563
3649
|
catch (error) {
|