humanish 0.57.0 → 0.59.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 +38 -0
- package/dist/actor-registry.d.ts +6 -2
- package/dist/actor-registry.js +12 -0
- package/dist/actor-registry.js.map +1 -1
- package/dist/cua-actor-lab.d.ts +3 -0
- package/dist/cua-actor-lab.js +75 -4
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-terminal-lab.js +44 -2
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/first-run-path.d.ts +39 -0
- package/dist/first-run-path.js +104 -0
- package/dist/first-run-path.js.map +1 -0
- package/dist/init-templates.d.ts +8 -0
- package/dist/init-templates.js +69 -0
- package/dist/init-templates.js.map +1 -1
- package/dist/init.d.ts +7 -0
- package/dist/init.js +62 -3
- package/dist/init.js.map +1 -1
- package/dist/lab-config.d.ts +19 -0
- package/dist/lab-config.js +17 -1
- package/dist/lab-config.js.map +1 -1
- package/dist/local-agent-appserver.d.ts +44 -0
- package/dist/local-agent-appserver.js +267 -0
- package/dist/local-agent-appserver.js.map +1 -0
- package/dist/local-agent-cli.d.ts +104 -0
- package/dist/local-agent-cli.js +337 -0
- package/dist/local-agent-cli.js.map +1 -0
- package/dist/program.js +21 -2
- package/dist/program.js.map +1 -1
- package/dist/run.js +9 -0
- package/dist/run.js.map +1 -1
- package/dist/tui-app.js +110 -110
- package/dist/tui-contract.d.ts +6 -0
- package/dist/tui-contract.js.map +1 -1
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +1 -1
- package/docs/ramp/README.md +29 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -67,6 +67,44 @@ npx skills add danielgwilson/humanish --skill humanish
|
|
|
67
67
|
The skill lives at [`skills/humanish/SKILL.md`](skills/humanish/SKILL.md)
|
|
68
68
|
for skills.sh discovery.
|
|
69
69
|
|
|
70
|
+
## A First Live Run Without a Provider API Key
|
|
71
|
+
|
|
72
|
+
A live study normally needs a provider API key. If you already have a coding
|
|
73
|
+
agent signed in — Codex on a ChatGPT plan, Claude Code on a Max plan — humanish
|
|
74
|
+
can use it as the participant's brain instead, and then the only credential it
|
|
75
|
+
needs is `E2B_API_KEY`.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
humanish doctor # says which local agents are installed and signed in
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
actors:
|
|
83
|
+
- type: local-agent # instead of openai-computer-use
|
|
84
|
+
persona: synthetic-new-user
|
|
85
|
+
mission: >-
|
|
86
|
+
...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
humanish never reads those credentials. It checks that the credential file
|
|
90
|
+
**exists**, spawns the CLI tool-restricted (`--sandbox read-only` for Codex,
|
|
91
|
+
`--allowedTools Read` for Claude Code) in a scratch directory, and hands it one
|
|
92
|
+
screenshot per turn. The agent only **decides**; humanish performs the action
|
|
93
|
+
inside the E2B sandbox, so nothing the persona chooses ever runs on your machine.
|
|
94
|
+
|
|
95
|
+
Three things to know before you rely on it:
|
|
96
|
+
|
|
97
|
+
- **It is not free.** Subscription usage consumes your own plan. Runs driven this
|
|
98
|
+
way record `estimatedCostUsd: null` with `reason: "no_token_usage"` rather than
|
|
99
|
+
`$0`, because `$0` would be untrue. Rate limits on those plans are built for
|
|
100
|
+
interactive coding; humanish fails closed with the CLI's own message rather
|
|
101
|
+
than retrying into them.
|
|
102
|
+
- **It is slower.** Roughly 9 seconds per turn against about 3 for a direct API
|
|
103
|
+
call, so give the lane a longer `execution.timeoutMs` than you would otherwise.
|
|
104
|
+
- **The evidence says which brain ran it.** The trace records
|
|
105
|
+
`ids.model: "codex (local, operator-authenticated)"`, so a local-agent run is
|
|
106
|
+
never silently compared against an API one.
|
|
107
|
+
|
|
70
108
|
## Public-Safety Boundary
|
|
71
109
|
|
|
72
110
|
Humanish is designed for public repositories and public issue queues. The
|
package/dist/actor-registry.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type ClaudeAgentSessionOptions, type ClaudeAgentSessionResult, type Cla
|
|
|
6
6
|
import { type CuaActorSessionOptions } from "./computer-use-actor.js";
|
|
7
7
|
import type { CuaLoopResult } from "./computer-use.js";
|
|
8
8
|
import { type ScriptedBrowserSessionOptions, type ScriptedBrowserSessionResult } from "./scripted-browser-actor.js";
|
|
9
|
-
export type ActorId = "codex-app-server" | "pi-agent-core" | "claude-agent-sdk" | "openai-computer-use" | "scripted-browser" | "codex-exec";
|
|
9
|
+
export type ActorId = "codex-app-server" | "pi-agent-core" | "claude-agent-sdk" | "openai-computer-use" | "local-agent" | "scripted-browser" | "codex-exec";
|
|
10
10
|
interface ActorDescriptorBase {
|
|
11
11
|
id: ActorId;
|
|
12
12
|
label: string;
|
|
@@ -30,6 +30,10 @@ export interface CuaActorDescriptor extends ActorDescriptorBase {
|
|
|
30
30
|
id: "openai-computer-use";
|
|
31
31
|
runSession(options: CuaActorSessionOptions): Promise<CuaLoopResult>;
|
|
32
32
|
}
|
|
33
|
+
export interface LocalAgentActorDescriptor extends ActorDescriptorBase {
|
|
34
|
+
id: "local-agent";
|
|
35
|
+
runSession(options: CuaActorSessionOptions): Promise<CuaLoopResult>;
|
|
36
|
+
}
|
|
33
37
|
export interface ScriptedBrowserActorDescriptor extends ActorDescriptorBase {
|
|
34
38
|
id: "scripted-browser";
|
|
35
39
|
runSession(options: ScriptedBrowserSessionOptions): Promise<ScriptedBrowserSessionResult>;
|
|
@@ -38,7 +42,7 @@ export interface TerminalActorDescriptor extends ActorDescriptorBase {
|
|
|
38
42
|
id: "codex-exec";
|
|
39
43
|
runSession(options: TerminalAgentSessionOptions): Promise<TerminalAgentSessionResult>;
|
|
40
44
|
}
|
|
41
|
-
export type ActorDescriptor = CodexActorDescriptor | PiActorDescriptor | ClaudeActorDescriptor | CuaActorDescriptor | ScriptedBrowserActorDescriptor | TerminalActorDescriptor;
|
|
45
|
+
export type ActorDescriptor = CodexActorDescriptor | PiActorDescriptor | ClaudeActorDescriptor | CuaActorDescriptor | LocalAgentActorDescriptor | ScriptedBrowserActorDescriptor | TerminalActorDescriptor;
|
|
42
46
|
/**
|
|
43
47
|
* REGISTRY CONTRACT: an actor whose capabilities include the "computer-use" lane is a
|
|
44
48
|
* CuaActorDescriptor — its runSession takes CuaActorSessionOptions and returns a CuaLoopResult.
|
package/dist/actor-registry.js
CHANGED
|
@@ -4,6 +4,7 @@ import { runTerminalAgentSession } from "./terminal-agent-actor.js";
|
|
|
4
4
|
import { piSessionToActorTrace } from "./pi-agent-core.js";
|
|
5
5
|
import { claudeSessionToActorTrace, runClaudeAgentSession } from "./claude-agent-sdk.js";
|
|
6
6
|
import { runCuaActorSession } from "./computer-use-actor.js";
|
|
7
|
+
import { LOCAL_AGENT_CAPABILITIES } from "./local-agent-cli.js";
|
|
7
8
|
import { OPENAI_RESPONSES_CU_CAPABILITIES } from "./openai-responses-cu.js";
|
|
8
9
|
import { runScriptedBrowserSession } from "./scripted-browser-actor.js";
|
|
9
10
|
/**
|
|
@@ -58,6 +59,17 @@ export const actorRegistry = {
|
|
|
58
59
|
},
|
|
59
60
|
// The ActorId names the actor slot (keeps the lane open for a future stagehand-cua provider);
|
|
60
61
|
// the trace's `provider` string stays "openai-responses-cu" (the concrete model adapter).
|
|
62
|
+
// The operator's own signed-in coding agent as the computer-use brain (Codex on a ChatGPT plan,
|
|
63
|
+
// Claude Code on a Max plan). Same lane, same loop, same evidence — the only difference is where
|
|
64
|
+
// the next action comes from, which is exactly why it is a provider swap and not a new lane.
|
|
65
|
+
// It exists so someone new can watch a persona drive a real desktop without first going to find
|
|
66
|
+
// an API key; the machine they are on very often already has one of these signed in.
|
|
67
|
+
"local-agent": {
|
|
68
|
+
id: "local-agent",
|
|
69
|
+
label: "Local coding agent (operator-authenticated)",
|
|
70
|
+
capabilities: LOCAL_AGENT_CAPABILITIES,
|
|
71
|
+
runSession: runCuaActorSession
|
|
72
|
+
},
|
|
61
73
|
"openai-computer-use": {
|
|
62
74
|
id: "openai-computer-use",
|
|
63
75
|
label: "OpenAI Computer Use",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor-registry.js","sourceRoot":"","sources":["../src/actor-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EAGzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,EAC7B,0BAA0B,EAC1B,6BAA6B,EAC7B,2BAA2B,EAC3B,uBAAuB,EAIxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,uBAAuB,EAGxB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAwB,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EAItB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAA+B,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"actor-registry.js","sourceRoot":"","sources":["../src/actor-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/cua-actor-lab.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type CuaActorDescriptor } from "./actor-registry.js";
|
|
|
5
5
|
import type { CuaActorSessionOptions } from "./computer-use-actor.js";
|
|
6
6
|
import type { CuaExecutor, CuaLoopResult, CuaProvider } from "./computer-use.js";
|
|
7
7
|
import type { ReasoningEffort } from "./reasoning-effort.js";
|
|
8
|
+
import { type LocalAgentId } from "./local-agent-cli.js";
|
|
8
9
|
import { type E2BDesktopModule, type E2BDesktopSandbox } from "./e2b-desktop-launch.js";
|
|
9
10
|
import { type DetachedTimers } from "./e2b-detached.js";
|
|
10
11
|
import { type DevicePreset } from "./device-presets.js";
|
|
@@ -512,6 +513,8 @@ export interface CuaLaneDeps {
|
|
|
512
513
|
config: LabConfig;
|
|
513
514
|
descriptor: CuaActorDescriptor;
|
|
514
515
|
appUrl: string;
|
|
516
|
+
/** When set, the computer-use brain is this locally-signed-in CLI instead of a keyed API. */
|
|
517
|
+
localAgent?: LocalAgentId;
|
|
515
518
|
cloneRoute: boolean;
|
|
516
519
|
/** desktop-cli (#495): a CLI studied at a desktop. Nothing is cloned and no browser is opened. */
|
|
517
520
|
desktopCliRoute?: boolean;
|
package/dist/cua-actor-lab.js
CHANGED
|
@@ -31,6 +31,8 @@ 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 { createLocalAgentProvider, detectLocalAgents } from "./local-agent-cli.js";
|
|
35
|
+
import { startAppServerSession } from "./local-agent-appserver.js";
|
|
34
36
|
import { createDesktopSandbox, loadE2BDesktopModule } from "./e2b-desktop-launch.js";
|
|
35
37
|
import { probeUrl, readDetachedLog, runDetachedStep, startDetachedProcess } from "./e2b-detached.js";
|
|
36
38
|
import { DEFAULT_SANDBOX_CATCH_PORT, collectCommsThread, collectExternalCommsThread, deployCommsCatch, externalCatchHealthy, externalInboxUrl, refreshInboxSurface, writeInboxSurface } from "./comms-sandbox-catch.js";
|
|
@@ -1273,6 +1275,9 @@ export function resolveSelfReportedFriction(session) {
|
|
|
1273
1275
|
export async function runCuaLane(spec, deps) {
|
|
1274
1276
|
const { config, appUrl, cloneRoute, localTreeRoute, serve, subjectRepo, subjectEnvNames } = deps;
|
|
1275
1277
|
const desktopCliRoute = deps.desktopCliRoute === true;
|
|
1278
|
+
// The local brain, when there is one. `appServer` owns a process, so the lane closes it.
|
|
1279
|
+
let appServer;
|
|
1280
|
+
let localAgentProvider;
|
|
1276
1281
|
const subjectEnvValues = config.subject.envValues ?? {};
|
|
1277
1282
|
const targetUrl = spec.targetUrl ?? appUrl;
|
|
1278
1283
|
const env = deps.env;
|
|
@@ -1560,6 +1565,24 @@ export async function runCuaLane(spec, deps) {
|
|
|
1560
1565
|
await openDesktopTerminal(desktop, deps.requestTimeoutMs, config.subject.product?.workdir);
|
|
1561
1566
|
await desktop.wait(BROWSER_SETTLE_MS).catch(() => undefined);
|
|
1562
1567
|
}
|
|
1568
|
+
// Start the brain BEFORE the first screenshot: the app-server handshake is ~500ms, and it
|
|
1569
|
+
// is paid here, while the sandbox is still settling, rather than inside turn one.
|
|
1570
|
+
if (deps.localAgent === "codex") {
|
|
1571
|
+
appServer = await startAppServerSession({
|
|
1572
|
+
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
1573
|
+
...(config.actors[0]?.model === undefined ? {} : { model: config.actors[0].model }),
|
|
1574
|
+
// The persona lives on the THREAD, so it is stated once instead of re-sent every turn.
|
|
1575
|
+
baseInstructions: spec.instructions
|
|
1576
|
+
});
|
|
1577
|
+
localAgentProvider = appServer.provider;
|
|
1578
|
+
}
|
|
1579
|
+
else if (deps.localAgent === "claude") {
|
|
1580
|
+
localAgentProvider = createLocalAgentProvider({
|
|
1581
|
+
agent: "claude",
|
|
1582
|
+
...(spec.reasoningEffort === undefined ? {} : { reasoningEffort: spec.reasoningEffort }),
|
|
1583
|
+
...(config.actors[0]?.model === undefined ? {} : { model: config.actors[0].model })
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1563
1586
|
// World is ready: release the pipeline gate so the remaining lanes may start.
|
|
1564
1587
|
provisioned = true;
|
|
1565
1588
|
signal(true);
|
|
@@ -1625,6 +1648,10 @@ export async function runCuaLane(spec, deps) {
|
|
|
1625
1648
|
: spec.instructions,
|
|
1626
1649
|
persona: spec.persona,
|
|
1627
1650
|
timeoutMs: deps.timeoutMs,
|
|
1651
|
+
// The brain is either a keyed API client or a CLI the operator is already signed in to.
|
|
1652
|
+
// Everything below this line — loop, executor, trace, affordances — is identical either
|
|
1653
|
+
// way, which is what makes a local-agent run comparable to an API one.
|
|
1654
|
+
...(localAgentProvider === undefined ? {} : { provider: localAgentProvider }),
|
|
1628
1655
|
openai: {
|
|
1629
1656
|
apiKey: deps.openaiApiKey,
|
|
1630
1657
|
...(config.actors[0]?.model ? { model: config.actors[0].model } : {}),
|
|
@@ -1687,6 +1714,9 @@ export async function runCuaLane(spec, deps) {
|
|
|
1687
1714
|
sessionError = redactText(deps.scrubKnownValues(toErrorMessage(error)));
|
|
1688
1715
|
}
|
|
1689
1716
|
finally {
|
|
1717
|
+
// The local brain owns a process. Close it before anything else can throw: a leaked
|
|
1718
|
+
// app-server per lane would outlive the run and keep a thread open on the operator's plan.
|
|
1719
|
+
appServer?.close();
|
|
1690
1720
|
// Stop the mid-run inbox-surface loop FIRST — before the teardown evidence drain below — so the two
|
|
1691
1721
|
// `cat`s never overlap and the final surface state is deterministic. A surface failure can never
|
|
1692
1722
|
// block teardown (the loop body is fully try/caught and this await is on its already-caught promise).
|
|
@@ -2371,14 +2401,48 @@ async function runCuaActorLabInScope(options) {
|
|
|
2371
2401
|
const redactRepoLabel = config.policies?.redactRepos ?? subjectEnvNames.includes("GITHUB_TOKEN");
|
|
2372
2402
|
const publicRepo = cloneRoute && subjectRepo ? (redactRepoLabel ? "repo-01" : subjectRepo) : undefined;
|
|
2373
2403
|
const hasGithubToken = subjectEnvNames.includes("GITHUB_TOKEN");
|
|
2374
|
-
//
|
|
2404
|
+
// The operator's own signed-in coding agent is the brain, so there is no provider key to ask
|
|
2405
|
+
// for — the entire point of the actor. E2B is still required: the persona needs a machine.
|
|
2406
|
+
const localAgentRoute = actorType === "local-agent";
|
|
2407
|
+
// Which local CLI, from its OWN field: `model` means the model, so that "Claude Code running
|
|
2408
|
+
// Opus" is sayable. Preflight below refuses when the chosen one is missing or signed out — that
|
|
2409
|
+
// news is worthless after a sandbox is paid for.
|
|
2410
|
+
const preferredLocalAgent = config.actors[0]?.localAgent ?? "codex";
|
|
2411
|
+
// Key-gating is route-aware: the in-process route uses the caller's OWN model + executor, and
|
|
2412
|
+
// the local-agent route uses a CLI the operator has already signed in to.
|
|
2375
2413
|
if (!dryRun && !inProcessRoute) {
|
|
2376
2414
|
const missingKeys = [
|
|
2377
|
-
...(openaiApiKey ? [] : ["OPENAI_API_KEY"]),
|
|
2415
|
+
...(openaiApiKey || localAgentRoute ? [] : ["OPENAI_API_KEY"]),
|
|
2378
2416
|
...(e2bApiKey ? [] : ["E2B_API_KEY"])
|
|
2379
2417
|
];
|
|
2380
2418
|
if (missingKeys.length > 0) {
|
|
2381
|
-
|
|
2419
|
+
// The moment someone new actually hits the wall. If a signed-in coding agent is sitting
|
|
2420
|
+
// right there, say so HERE rather than making them go and find an API key — that detour is
|
|
2421
|
+
// where most people trying humanish stop.
|
|
2422
|
+
const suggestion = missingKeys.includes("OPENAI_API_KEY")
|
|
2423
|
+
? await (async () => {
|
|
2424
|
+
const ready = (await detectLocalAgents()).filter((agent) => agent.credentialsPresent);
|
|
2425
|
+
return ready.length === 0
|
|
2426
|
+
? ""
|
|
2427
|
+
: ` You have ${ready.map((agent) => agent.label).join(" and ")} signed in on this machine`
|
|
2428
|
+
+ ` — set actors[0].type: local-agent to use ${ready.length === 1 ? "it" : "one"} instead of a key.`;
|
|
2429
|
+
})()
|
|
2430
|
+
: "";
|
|
2431
|
+
return fail("HUMANISH_CUA_LAB_KEYS_MISSING", `Live computer-use labs need ${missingKeys.join(" and ")} in the environment (values are never persisted). ${describeMissingKeys(missingKeys, env)}${suggestion}`, descriptor.id);
|
|
2432
|
+
}
|
|
2433
|
+
if (localAgentRoute) {
|
|
2434
|
+
// Refuse HERE, before a sandbox exists. "codex is not installed" discovered after the
|
|
2435
|
+
// machine is paid for is the same information delivered at the worst possible moment.
|
|
2436
|
+
const available = await detectLocalAgents();
|
|
2437
|
+
const chosen = available.find((agent) => agent.id === preferredLocalAgent);
|
|
2438
|
+
if (chosen === undefined) {
|
|
2439
|
+
return fail("HUMANISH_CUA_LAB_KEYS_MISSING", `actors[0].type: local-agent needs the ${preferredLocalAgent} CLI on PATH and signed in. `
|
|
2440
|
+
+ `Install it, or set OPENAI_API_KEY and use actors[0].type: openai-computer-use instead.`, descriptor.id);
|
|
2441
|
+
}
|
|
2442
|
+
if (!chosen.credentialsPresent) {
|
|
2443
|
+
return fail("HUMANISH_CUA_LAB_KEYS_MISSING", `${chosen.label} is installed but not signed in — run \`${chosen.bin}\` once to log in. `
|
|
2444
|
+
+ "humanish never reads its credentials; it only checks that the file exists.", descriptor.id);
|
|
2445
|
+
}
|
|
2382
2446
|
}
|
|
2383
2447
|
const missingSubjectEnv = subjectEnvNames.filter((name) => !env[name]?.trim());
|
|
2384
2448
|
if (missingSubjectEnv.length > 0) {
|
|
@@ -2466,6 +2530,7 @@ async function runCuaActorLabInScope(options) {
|
|
|
2466
2530
|
config,
|
|
2467
2531
|
descriptor,
|
|
2468
2532
|
appUrl,
|
|
2533
|
+
...(localAgentRoute ? { localAgent: preferredLocalAgent } : {}),
|
|
2469
2534
|
cloneRoute,
|
|
2470
2535
|
desktopCliRoute,
|
|
2471
2536
|
localTreeRoute,
|
|
@@ -4227,7 +4292,13 @@ export function buildCuaFanoutBundle(args) {
|
|
|
4227
4292
|
// collapse the run to one word; this does not (docs/principles/three-roles.md).
|
|
4228
4293
|
const terminalOutcomes = (outcomes ?? []).filter((outcome) => outcome?.session?.status !== undefined);
|
|
4229
4294
|
const participants = terminalOutcomes.length > 0
|
|
4230
|
-
? tallyParticipantOutcomes(
|
|
4295
|
+
? tallyParticipantOutcomes(
|
|
4296
|
+
// A NO-ENGAGEMENT lane is not a participant who reached the goal. It said "done" having
|
|
4297
|
+
// taken zero actions and said nothing, and `passedLanes` below already refuses to count
|
|
4298
|
+
// it — but `reachedGoal` was reading the trace status directly, so one run could be both
|
|
4299
|
+
// "not a passed lane" AND "1/1 reached the goal". The headline number a researcher reads
|
|
4300
|
+
// first was the dishonest one. Found by a provider bug that ended a study on turn one.
|
|
4301
|
+
terminalOutcomes.map((outcome) => (outcome.noEngagement === true ? "incomplete" : outcome.session.status)),
|
|
4231
4302
|
// A participant who reached the goal AND told you the road there was broken is the most
|
|
4232
4303
|
// useful result a study produces; reporting only the outcome would bury it.
|
|
4233
4304
|
terminalOutcomes.map((outcome) => outcome.reportedFriction === true))
|