humanish 0.30.0 → 0.32.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 +12 -0
- package/dist/comms-types.d.ts +1 -1
- package/dist/cua-actor-lab.d.ts +8 -0
- package/dist/cua-actor-lab.js +29 -7
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-terminal-lab.d.ts +13 -0
- package/dist/e2b-terminal-lab.js +77 -9
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/index.d.ts +3 -9
- package/dist/index.js +2 -11
- package/dist/index.js.map +1 -1
- package/dist/lab-config.js +7 -0
- package/dist/lab-config.js.map +1 -1
- package/docs/contracts/schemas.md +18 -1
- package/docs/goals/current.md +22 -17
- package/docs/ramp/README.md +9 -4
- package/package.json +1 -1
- package/skills/humanish/SKILL.md +28 -1
package/README.md
CHANGED
|
@@ -262,6 +262,18 @@ you own (a Vercel preview, staging), use an `app-url` subject with
|
|
|
262
262
|
only declared subject env names do. `humanish init` scaffolds an example at
|
|
263
263
|
`humanish/labs/cua-browser.yaml`.
|
|
264
264
|
|
|
265
|
+
**Off-app email/SMS verification (`comms`).** When a flow is gated behind an email
|
|
266
|
+
or SMS the app itself sends — a signup verification link, a one-time code, a magic
|
|
267
|
+
link — add a `comms:` block to the lab. Humanish redirects the app's email-API sends
|
|
268
|
+
(via one adopter-named env var, e.g. `RESEND_API_URL`) into a catch **inside** the
|
|
269
|
+
sandbox, so nothing leaves the machine; the persona then opens a synthetic inbox to
|
|
270
|
+
read and click through, and the run bundle gets a digest-only
|
|
271
|
+
`humanish.comms-thread.v1` artifact (from/to/subject/link digests + an OTP count —
|
|
272
|
+
no raw address, link, or code persists). It works on the single-lane clone/local-tree
|
|
273
|
+
route and the concurrent shared-world route, and is vendor-neutral (Resend/SendGrid
|
|
274
|
+
shaped, or a custom profile). See `docs/contracts/schemas.md` for the full `comms:`
|
|
275
|
+
shape.
|
|
276
|
+
|
|
265
277
|
**Screenshots are full-fidelity by default.** Run bundles live in gitignored
|
|
266
278
|
`.humanish/`, so the Observer shows exactly what the persona saw — the point of
|
|
267
279
|
simming your own app. Set `policies.redactScreenshots: true` to persist blurred
|
package/dist/comms-types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface CommsAddress {
|
|
|
4
4
|
channel: CommsChannelKind;
|
|
5
5
|
/** Which lane owns this inbox. */
|
|
6
6
|
actorId: string;
|
|
7
|
-
/** Runtime-only raw address, e.g.
|
|
7
|
+
/** Runtime-only raw address, e.g. user-07@example.test | +15550137. */
|
|
8
8
|
value: string;
|
|
9
9
|
/** sha256-short(value) — the only form persisted (redaction.digestText). */
|
|
10
10
|
digest: string;
|
package/dist/cua-actor-lab.d.ts
CHANGED
|
@@ -612,6 +612,14 @@ export declare function captureDesktopBrowserGeometry(args: {
|
|
|
612
612
|
viewport?: RunDesktopGeometry["viewport"];
|
|
613
613
|
warnings: string[];
|
|
614
614
|
}>;
|
|
615
|
+
/**
|
|
616
|
+
* A goal_satisfied lane counts as a self-reported blocker ONLY when its final narrative contradicts
|
|
617
|
+
* the goal AND the run's own stop predicate did NOT fire. A matched stopWhen is independent,
|
|
618
|
+
* structured completion evidence, so it overrides a text scan of the free-form narrative — which can
|
|
619
|
+
* otherwise trip on the subject app's OWN quoted copy (e.g. a relayed "cannot be undone" banner).
|
|
620
|
+
* Returns the offending reason, or undefined when the lane is a clean pass. Exported for testing.
|
|
621
|
+
*/
|
|
622
|
+
export declare function resolveSelfReportedBlocker(session: CuaLoopResult | undefined): string | undefined;
|
|
615
623
|
/**
|
|
616
624
|
* Run ONE E2B desktop lane end-to-end: create the sandbox (per-lane metadata + the lane's device
|
|
617
625
|
* resolution), prepareDesktop, verify geometry, (clone+serve+seed the subject per lane), open the
|
package/dist/cua-actor-lab.js
CHANGED
|
@@ -981,7 +981,7 @@ async function startDesktopStream(desktop, browserWindowId) {
|
|
|
981
981
|
}
|
|
982
982
|
}
|
|
983
983
|
function completionReasonContradictsGoal(reason) {
|
|
984
|
-
const text = stripNegatedNonBlockerPhrases(reason.toLowerCase());
|
|
984
|
+
const text = stripQuotedSpans(stripNegatedNonBlockerPhrases(reason.toLowerCase()));
|
|
985
985
|
return /\b(can'?t|cannot|could not|unable|blocked|blocker|failed|invalid|not set)\b/.test(text)
|
|
986
986
|
|| /\b(shows|showing|hit|encountered|returned|got)\b.{0,80}\berror\b/.test(text)
|
|
987
987
|
|| /\berror[:.]/.test(text)
|
|
@@ -993,11 +993,37 @@ function stripNegatedNonBlockerPhrases(text) {
|
|
|
993
993
|
.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, "")
|
|
994
994
|
.replace(/\bnot\s+(?:blocked|a blocker|an error|failed)\b/g, "");
|
|
995
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Remove double-quoted spans and markdown blockquote lines before the blocker scan, so a persona
|
|
998
|
+
* that faithfully QUOTES the subject app's own copy (e.g. a banner reading "cannot be undone") is
|
|
999
|
+
* not misread as the actor reporting its OWN blocker. Only double quotes (straight and smart) and
|
|
1000
|
+
* `>` blockquotes are stripped — never single quotes, which would mangle contractions like `can't`.
|
|
1001
|
+
*/
|
|
1002
|
+
function stripQuotedSpans(text) {
|
|
1003
|
+
return text
|
|
1004
|
+
.replace(/"[^"]*"/g, " ")
|
|
1005
|
+
.replace(/“[^”]*”/g, " ")
|
|
1006
|
+
.replace(/^\s*>.*$/gm, " ");
|
|
1007
|
+
}
|
|
996
1008
|
function traceHasStopWhenMatch(session) {
|
|
997
1009
|
return session.trace.items.some((item) => item.kind === "notice"
|
|
998
1010
|
&& item.status === "matched"
|
|
999
1011
|
&& item.title.startsWith("stopWhen matched"));
|
|
1000
1012
|
}
|
|
1013
|
+
/**
|
|
1014
|
+
* A goal_satisfied lane counts as a self-reported blocker ONLY when its final narrative contradicts
|
|
1015
|
+
* the goal AND the run's own stop predicate did NOT fire. A matched stopWhen is independent,
|
|
1016
|
+
* structured completion evidence, so it overrides a text scan of the free-form narrative — which can
|
|
1017
|
+
* otherwise trip on the subject app's OWN quoted copy (e.g. a relayed "cannot be undone" banner).
|
|
1018
|
+
* Returns the offending reason, or undefined when the lane is a clean pass. Exported for testing.
|
|
1019
|
+
*/
|
|
1020
|
+
export function resolveSelfReportedBlocker(session) {
|
|
1021
|
+
return session?.completionReason === "goal_satisfied"
|
|
1022
|
+
&& completionReasonContradictsGoal(session.reason)
|
|
1023
|
+
&& !traceHasStopWhenMatch(session)
|
|
1024
|
+
? session.reason
|
|
1025
|
+
: undefined;
|
|
1026
|
+
}
|
|
1001
1027
|
/**
|
|
1002
1028
|
* Run ONE E2B desktop lane end-to-end: create the sandbox (per-lane metadata + the lane's device
|
|
1003
1029
|
* resolution), prepareDesktop, verify geometry, (clone+serve+seed the subject per lane), open the
|
|
@@ -1468,9 +1494,7 @@ export async function runCuaLane(spec, deps) {
|
|
|
1468
1494
|
if (noEngagement) {
|
|
1469
1495
|
warnings.push("Actor returned goal_satisfied with ZERO actions and ZERO messages — it likely saw a blank or still-loading screen and stopped without engaging. NOT counted as a pass. Check the screenshot; raise execution.timeoutMs or confirm the subject painted before the first turn.");
|
|
1470
1496
|
}
|
|
1471
|
-
const blockerReason =
|
|
1472
|
-
? session.reason
|
|
1473
|
-
: undefined;
|
|
1497
|
+
const blockerReason = resolveSelfReportedBlocker(session);
|
|
1474
1498
|
const selfReportedBlocker = blockerReason !== undefined;
|
|
1475
1499
|
if (selfReportedBlocker) {
|
|
1476
1500
|
warnings.push(`Actor returned goal_satisfied while its final message describes a blocker or asks for missing instructions — NOT counted as a pass: ${redactText(deps.scrubKnownValues(blockerReason))}`);
|
|
@@ -1538,9 +1562,7 @@ async function runInProcessLane(spec, deps) {
|
|
|
1538
1562
|
if (noEngagement) {
|
|
1539
1563
|
warnings.push("Actor returned goal_satisfied with ZERO actions and ZERO messages — it likely saw a blank or still-loading screen and stopped without engaging. NOT counted as a pass. Check the screenshot; raise execution.timeoutMs or confirm the subject painted before the first turn.");
|
|
1540
1564
|
}
|
|
1541
|
-
const blockerReason =
|
|
1542
|
-
? session.reason
|
|
1543
|
-
: undefined;
|
|
1565
|
+
const blockerReason = resolveSelfReportedBlocker(session);
|
|
1544
1566
|
const selfReportedBlocker = blockerReason !== undefined;
|
|
1545
1567
|
if (selfReportedBlocker) {
|
|
1546
1568
|
warnings.push(`Actor returned goal_satisfied while its final message describes a blocker or asks for missing instructions — NOT counted as a pass: ${blockerReason}`);
|