humanish 0.65.0 → 0.67.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 +14 -3
- package/dist/actor-contract.d.ts +2 -1
- package/dist/actor-contract.js.map +1 -1
- package/dist/computer-use.d.ts +17 -0
- package/dist/computer-use.js +114 -5
- package/dist/computer-use.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +23 -0
- package/dist/cua-actor-lab.js +90 -19
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-desktop-launch.d.ts +11 -0
- package/dist/e2b-desktop-launch.js.map +1 -1
- package/dist/e2b-terminal-lab.js +20 -1
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/lab-config.d.ts +17 -0
- package/dist/lab-config.js +14 -0
- package/dist/lab-config.js.map +1 -1
- package/dist/local-agent-claude-session.d.ts +45 -0
- package/dist/local-agent-claude-session.js +201 -0
- package/dist/local-agent-claude-session.js.map +1 -0
- package/dist/local-agent-cli.d.ts +2 -1
- package/dist/local-agent-cli.js +1 -1
- package/dist/local-agent-cli.js.map +1 -1
- package/dist/program.d.ts +3 -0
- package/dist/program.js +64 -6
- package/dist/program.js.map +1 -1
- package/dist/stats.d.ts +75 -0
- package/dist/stats.js +196 -0
- package/dist/stats.js.map +1 -0
- package/dist/telemetry.d.ts +14 -0
- package/dist/telemetry.js +109 -1
- package/dist/telemetry.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 { adapterScoreFailureMessage, applyBrowserAdapterHooks } from "./adapter-
|
|
|
31
31
|
import { actorRegistry, isCuaActorDescriptor } from "./actor-registry.js";
|
|
32
32
|
import { CHROMIUM_EVIDENCE_HYGIENE_FLAGS, chromiumEvidenceProfilePreferencesJson } from "./browser-evidence-hygiene.js";
|
|
33
33
|
import { DEFAULT_OPENAI_CU_MODEL } from "./openai-responses-cu.js";
|
|
34
|
-
import {
|
|
34
|
+
import { detectLocalAgents } from "./local-agent-cli.js";
|
|
35
35
|
import { startAppServerSession } from "./local-agent-appserver.js";
|
|
36
|
+
import { startClaudeSession } from "./local-agent-claude-session.js";
|
|
36
37
|
import { createDesktopSandbox, loadE2BDesktopModule } from "./e2b-desktop-launch.js";
|
|
37
38
|
import { probeUrl, readDetachedLog, runDetachedStep, startDetachedProcess } from "./e2b-detached.js";
|
|
38
39
|
import { DEFAULT_SANDBOX_CATCH_PORT, collectCommsThread, collectExternalCommsThread, deployCommsCatch, externalCatchHealthy, externalInboxUrl, refreshInboxSurface, writeInboxSurface } from "./comms-sandbox-catch.js";
|
|
@@ -1149,7 +1150,7 @@ async function openDesktopTerminal(desktop, requestTimeoutMs, workdir) {
|
|
|
1149
1150
|
// a study that measures our own mojibake against an unconfigured template would be measuring
|
|
1150
1151
|
// the template. The PRODUCT-side fix (an ASCII fallback when the locale is not UTF-8) is in
|
|
1151
1152
|
// src/terminal-encoding.ts, and it is the one that matters for real users.
|
|
1152
|
-
` (cd ${shellSingleQuote(dir)} 2>/dev/null || cd /home/user; DISPLAY=:0 LANG=C.UTF-8 LC_ALL=C.UTF-8 nohup "$candidate" >/dev/null 2>&1 &)`,
|
|
1153
|
+
` (cd ${shellSingleQuote(dir)} 2>/dev/null || cd /home/user; DISPLAY=:0 LANG=C.UTF-8 LC_ALL=C.UTF-8 HUMANISH_STUDY_PARTICIPANT=1 nohup "$candidate" >/dev/null 2>&1 &)`,
|
|
1153
1154
|
" sleep 3",
|
|
1154
1155
|
' echo "humanish: opened $candidate"',
|
|
1155
1156
|
" exit 0",
|
|
@@ -1178,6 +1179,16 @@ async function startDesktopStream(desktop, browserWindowId) {
|
|
|
1178
1179
|
await desktop.stream.start({ requireAuth: true });
|
|
1179
1180
|
}
|
|
1180
1181
|
}
|
|
1182
|
+
// "can't" followed by a PERCEPTION verb describes what the screen showed, not an inability to
|
|
1183
|
+
// proceed: "the canvas truncates it so you can't even read the whole thing", "I can't tell from
|
|
1184
|
+
// the screen whether the rename is persisted", "so I could not read its full description". Five
|
|
1185
|
+
// of five completed live runs on 2026-09-01 (two on drawDB, three on the planted benchmark app)
|
|
1186
|
+
// were refused as "not a credible pass" on exactly these sentences, every one a defect report
|
|
1187
|
+
// written AFTER the participant reached the goal. The more precisely a participant describes a
|
|
1188
|
+
// display defect, the more likely the scan was to refuse the run — the incentive inversion #453
|
|
1189
|
+
// fixed for resolved arcs, back in a new shape. "could not complete", "could not connect",
|
|
1190
|
+
// "unable to get focus" still count: those name an inability to act.
|
|
1191
|
+
const PERCEPTION_AFTER_MODAL = /\b(can'?t|cannot|could ?not|couldn'?t|unable to|wasn'?t able to)\s+(even\s+|quite\s+|really\s+|fully\s+)?(read|see|tell|view|make out|verify|confirm|be sure|be certain|judge|know)\b/g;
|
|
1181
1192
|
function hasBlockerLanguage(text) {
|
|
1182
1193
|
return /\b(can'?t|cannot|could not|unable|blocked|blocker|failed|invalid|not set)\b/.test(text)
|
|
1183
1194
|
|| /\b(shows|showing|hit|encountered|returned|got)\b.{0,80}\berror\b/.test(text)
|
|
@@ -1193,7 +1204,11 @@ function completionReasonContradictsGoal(reason) {
|
|
|
1193
1204
|
* first — failure narration the participant itself reports as overcome is friction on the
|
|
1194
1205
|
* road, not a blocker at the destination (#453). */
|
|
1195
1206
|
function completionReasonBlocksVerdict(reason) {
|
|
1196
|
-
|
|
1207
|
+
// Perception phrases are stripped for the VERDICT only: "I could not read the full description"
|
|
1208
|
+
// is friction worth a tally count and a feedback candidate (the friction scan above keeps it),
|
|
1209
|
+
// and it is not a reason to refuse the pass.
|
|
1210
|
+
return hasBlockerLanguage(stripResolvedArcSegments(stripQuotedSpans(stripNegatedNonBlockerPhrases(reason.toLowerCase())))
|
|
1211
|
+
.replace(PERCEPTION_AFTER_MODAL, ""));
|
|
1197
1212
|
}
|
|
1198
1213
|
// A failure segment counts as a resolved arc when the recovery is self-reported either in the
|
|
1199
1214
|
// SAME segment ("the import failed but then went through") or — the common report shape — in the
|
|
@@ -1221,6 +1236,10 @@ function stripResolvedArcSegments(text) {
|
|
|
1221
1236
|
}
|
|
1222
1237
|
function stripNegatedNonBlockerPhrases(text) {
|
|
1223
1238
|
return text
|
|
1239
|
+
// FIRST, before the narrower rules eat the "no blockers" and leave "encountered ... error"
|
|
1240
|
+
// behind: "I encountered no blockers or unclear error output." refused a clean passing run on
|
|
1241
|
+
// 2026-09-01. A verb of encounter followed by "no" negates the whole clause, so drop the clause.
|
|
1242
|
+
.replace(/\b(?:encountered|hit|saw|found|met|had|got|ran into)\s+no\s+[^.!?\n]*/g, " ")
|
|
1224
1243
|
.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, "")
|
|
1225
1244
|
.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, "")
|
|
1226
1245
|
.replace(/\bnot\s+(?:blocked|a blocker|an error|failed)\b/g, "")
|
|
@@ -1286,8 +1305,10 @@ export function resolveSelfReportedFriction(session) {
|
|
|
1286
1305
|
export async function runCuaLane(spec, deps) {
|
|
1287
1306
|
const { config, appUrl, cloneRoute, localTreeRoute, serve, subjectRepo, subjectEnvNames } = deps;
|
|
1288
1307
|
const desktopCliRoute = deps.desktopCliRoute === true;
|
|
1289
|
-
// The local brain, when there is one. `appServer`
|
|
1308
|
+
// The local brain, when there is one. `appServer` / `claudeSession` own a process, so the lane
|
|
1309
|
+
// closes it.
|
|
1290
1310
|
let appServer;
|
|
1311
|
+
let claudeSession;
|
|
1291
1312
|
let localAgentProvider;
|
|
1292
1313
|
const subjectEnvValues = config.subject.envValues ?? {};
|
|
1293
1314
|
const targetUrl = spec.targetUrl ?? appUrl;
|
|
@@ -1588,11 +1609,14 @@ export async function runCuaLane(spec, deps) {
|
|
|
1588
1609
|
localAgentProvider = appServer.provider;
|
|
1589
1610
|
}
|
|
1590
1611
|
else if (deps.localAgent === "claude") {
|
|
1591
|
-
|
|
1592
|
-
|
|
1612
|
+
// One session for the whole run, like the codex thread above (#520). The one-shot
|
|
1613
|
+
// provider (createLocalAgentProvider) spawned `claude -p` per turn, and every turn
|
|
1614
|
+
// started with no memory of the last.
|
|
1615
|
+
claudeSession = await startClaudeSession({
|
|
1593
1616
|
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
1594
1617
|
...(config.actors[0]?.model === undefined ? {} : { model: config.actors[0].model })
|
|
1595
1618
|
});
|
|
1619
|
+
localAgentProvider = claudeSession.provider;
|
|
1596
1620
|
}
|
|
1597
1621
|
// World is ready: release the pipeline gate so the remaining lanes may start.
|
|
1598
1622
|
provisioned = true;
|
|
@@ -1728,6 +1752,7 @@ export async function runCuaLane(spec, deps) {
|
|
|
1728
1752
|
// The local brain owns a process. Close it before anything else can throw: a leaked
|
|
1729
1753
|
// app-server per lane would outlive the run and keep a thread open on the operator's plan.
|
|
1730
1754
|
appServer?.close();
|
|
1755
|
+
await claudeSession?.close();
|
|
1731
1756
|
// Stop the mid-run inbox-surface loop FIRST — before the teardown evidence drain below — so the two
|
|
1732
1757
|
// `cat`s never overlap and the final surface state is deterministic. A surface failure can never
|
|
1733
1758
|
// block teardown (the loop body is fully try/caught and this await is on its already-caught promise).
|
|
@@ -2963,13 +2988,17 @@ async function runCuaActorLabInScope(options) {
|
|
|
2963
2988
|
message: outcome?.sessionError
|
|
2964
2989
|
?? (outcome?.noEngagement
|
|
2965
2990
|
? "Actor took no actions and produced no message (likely a blank/still-loading screen); not a credible goal_satisfied."
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2991
|
+
// The lane result (toLaneResult) named this refusal; the N=1 envelope fell through to
|
|
2992
|
+
// "did not produce a terminal session", which is false — it produced one and refused it.
|
|
2993
|
+
: outcome?.selfReportedBlocker
|
|
2994
|
+
? "Actor reported goal_satisfied while its final message described a blocker or asked for missing instructions; not a credible pass."
|
|
2995
|
+
: observer.ok
|
|
2996
|
+
? outcome?.session?.completionReason === "harness_error"
|
|
2997
|
+
? `Computer-use session ended with a harness error: ${outcome.session.reason}`
|
|
2998
|
+
: outcome?.session?.status !== "passed"
|
|
2999
|
+
? `Computer-use session ended with ${outcome?.session?.status ?? "unknown"}: ${outcome?.session?.reason ?? "no terminal reason"}`
|
|
3000
|
+
: "Computer-use lab did not produce a terminal session."
|
|
3001
|
+
: observer.error?.message ?? "Observer failed for the computer-use lab run.")
|
|
2973
3002
|
};
|
|
2974
3003
|
}
|
|
2975
3004
|
const failingLane = (outcomes ?? []).find((outcome) => !laneOk(outcome));
|
|
@@ -3117,6 +3146,15 @@ function buildSingleLaneBundle(args) {
|
|
|
3117
3146
|
captureRedaction: args.redactScreenshots ? "blurred" : "raw",
|
|
3118
3147
|
...(outcome?.session ? { session: outcome.session } : {}),
|
|
3119
3148
|
...(outcome?.sessionError ? { sessionError: outcome.sessionError } : {}),
|
|
3149
|
+
...(outcome === undefined
|
|
3150
|
+
? {}
|
|
3151
|
+
: {
|
|
3152
|
+
credibility: {
|
|
3153
|
+
noEngagement: outcome.noEngagement === true,
|
|
3154
|
+
selfReportedBlocker: outcome.selfReportedBlocker === true,
|
|
3155
|
+
reportedFriction: outcome.reportedFriction === true
|
|
3156
|
+
}
|
|
3157
|
+
}),
|
|
3120
3158
|
source: args.source,
|
|
3121
3159
|
...(args.inProgress === undefined ? {} : { inProgress: args.inProgress }),
|
|
3122
3160
|
...(args.subjectProvenance === undefined ? {} : { subjectProvenance: args.subjectProvenance }),
|
|
@@ -3871,22 +3909,36 @@ export function buildCuaBundle(args) {
|
|
|
3871
3909
|
const singleStudyTasks = args.inProgress !== true && args.session?.trace.taskFunnel !== undefined
|
|
3872
3910
|
? aggregateTaskFunnels([args.session.trace.taskFunnel])
|
|
3873
3911
|
: undefined;
|
|
3912
|
+
// What happened to the participant, as the LANE judged it — the same rule the fan-out roll-up
|
|
3913
|
+
// applies (participantStatusForOutcome). Before #476 this read the actor's own status, so a
|
|
3914
|
+
// run the lane refused as "not a credible pass" was written up as verdict pass, 1/1 reached
|
|
3915
|
+
// the goal, and every projection of the bundle (Observer tally, `runs`, the status index)
|
|
3916
|
+
// repeated it. Found on a real drawDB run whose participant wrote "Blocked after partial
|
|
3917
|
+
// completion".
|
|
3918
|
+
const participantStatus = args.session === undefined
|
|
3919
|
+
? undefined
|
|
3920
|
+
: participantStatusForCredibility(args.session.status, args.credibility);
|
|
3921
|
+
const credibilityNote = args.session === undefined || participantStatus === args.session.status
|
|
3922
|
+
? undefined
|
|
3923
|
+
: args.credibility?.noEngagement === true
|
|
3924
|
+
? "Not counted as a pass: the participant took no actions and said nothing."
|
|
3925
|
+
: "Not counted as a pass: the participant's final message described a blocker.";
|
|
3874
3926
|
const review = {
|
|
3875
3927
|
schema: REVIEW_SCHEMA,
|
|
3876
3928
|
verdict: args.inProgress === true
|
|
3877
3929
|
? "contract_proof_only"
|
|
3878
|
-
:
|
|
3879
|
-
? verdictForStatus(
|
|
3930
|
+
: participantStatus !== undefined
|
|
3931
|
+
? verdictForStatus(participantStatus)
|
|
3880
3932
|
: args.sessionError
|
|
3881
3933
|
? "fail"
|
|
3882
3934
|
: "contract_proof_only",
|
|
3883
3935
|
// One lane is still a study with a denominator of one, and saying so keeps a single-lane
|
|
3884
3936
|
// result from being read as though it generalized.
|
|
3885
|
-
...(
|
|
3886
|
-
? { participants: tallyParticipantOutcomes([args.
|
|
3937
|
+
...(participantStatus !== undefined && args.inProgress !== true
|
|
3938
|
+
? { participants: tallyParticipantOutcomes([participantStatus], [args.credibility?.reportedFriction === true]) }
|
|
3887
3939
|
: {}),
|
|
3888
3940
|
...(singleStudyTasks === undefined ? {} : { tasks: singleStudyTasks }),
|
|
3889
|
-
summary: reason
|
|
3941
|
+
summary: credibilityNote === undefined ? reason : `${credibilityNote} ${reason}`,
|
|
3890
3942
|
gaps: args.session || args.sessionError
|
|
3891
3943
|
? []
|
|
3892
3944
|
: args.inProgress === true
|
|
@@ -4309,7 +4361,10 @@ export function buildCuaFanoutBundle(args) {
|
|
|
4309
4361
|
// it — but `reachedGoal` was reading the trace status directly, so one run could be both
|
|
4310
4362
|
// "not a passed lane" AND "1/1 reached the goal". The headline number a researcher reads
|
|
4311
4363
|
// first was the dishonest one. Found by a provider bug that ended a study on turn one.
|
|
4312
|
-
terminalOutcomes.map((outcome) => (outcome.
|
|
4364
|
+
terminalOutcomes.map((outcome) => participantStatusForCredibility(outcome.session.status, {
|
|
4365
|
+
noEngagement: outcome.noEngagement === true,
|
|
4366
|
+
selfReportedBlocker: outcome.selfReportedBlocker === true
|
|
4367
|
+
})),
|
|
4313
4368
|
// A participant who reached the goal AND told you the road there was broken is the most
|
|
4314
4369
|
// useful result a study produces; reporting only the outcome would bury it.
|
|
4315
4370
|
terminalOutcomes.map((outcome) => outcome.reportedFriction === true))
|
|
@@ -4486,6 +4541,22 @@ function providerResourcesForOutcome(args) {
|
|
|
4486
4541
|
}
|
|
4487
4542
|
}];
|
|
4488
4543
|
}
|
|
4544
|
+
/**
|
|
4545
|
+
* The status a participant is TALLIED under, given what the lane made of the session. A
|
|
4546
|
+
* goal_satisfied claim with zero engagement is a session that ran out before anything happened;
|
|
4547
|
+
* one whose final message describes a blocker is a participant who could not proceed and said so.
|
|
4548
|
+
* Both keep their trace status (the claim is evidence); neither is a participant who reached the
|
|
4549
|
+
* goal. One rule for the single lane and the fan-out roll-up (#476).
|
|
4550
|
+
*/
|
|
4551
|
+
export function participantStatusForCredibility(status, credibility) {
|
|
4552
|
+
if (status !== "passed" || credibility === undefined)
|
|
4553
|
+
return status;
|
|
4554
|
+
if (credibility.noEngagement)
|
|
4555
|
+
return "incomplete";
|
|
4556
|
+
if (credibility.selfReportedBlocker)
|
|
4557
|
+
return "blocked";
|
|
4558
|
+
return status;
|
|
4559
|
+
}
|
|
4489
4560
|
function verdictForStatus(status) {
|
|
4490
4561
|
switch (status) {
|
|
4491
4562
|
case "passed":
|