humanish 0.94.0 → 0.96.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.
Files changed (95) hide show
  1. package/README.md +10 -0
  2. package/dist/comms-agentmail.d.ts +25 -0
  3. package/dist/comms-agentmail.js +418 -0
  4. package/dist/comms-agentmail.js.map +1 -0
  5. package/dist/comms-connections.d.ts +52 -0
  6. package/dist/comms-connections.js +123 -0
  7. package/dist/comms-connections.js.map +1 -0
  8. package/dist/comms-lease-store.d.ts +90 -0
  9. package/dist/comms-lease-store.js +489 -0
  10. package/dist/comms-lease-store.js.map +1 -0
  11. package/dist/comms-receiving-evidence.d.ts +46 -0
  12. package/dist/comms-receiving-evidence.js +35 -0
  13. package/dist/comms-receiving-evidence.js.map +1 -0
  14. package/dist/comms-receiving-inbox.d.ts +16 -0
  15. package/dist/comms-receiving-inbox.js +410 -0
  16. package/dist/comms-receiving-inbox.js.map +1 -0
  17. package/dist/comms-receiving-runtime.d.ts +25 -0
  18. package/dist/comms-receiving-runtime.js +45 -0
  19. package/dist/comms-receiving-runtime.js.map +1 -0
  20. package/dist/comms-receiving-types.d.ts +98 -0
  21. package/dist/comms-receiving-types.js +2 -0
  22. package/dist/comms-receiving-types.js.map +1 -0
  23. package/dist/comms-receiving.d.ts +58 -0
  24. package/dist/comms-receiving.js +554 -0
  25. package/dist/comms-receiving.js.map +1 -0
  26. package/dist/comms-setup.d.ts +40 -0
  27. package/dist/comms-setup.js +121 -0
  28. package/dist/comms-setup.js.map +1 -0
  29. package/dist/concurrent-shared-world-lab.js +662 -624
  30. package/dist/concurrent-shared-world-lab.js.map +1 -1
  31. package/dist/cua-actor-lab.d.ts +4 -1
  32. package/dist/cua-actor-lab.js +98 -17
  33. package/dist/cua-actor-lab.js.map +1 -1
  34. package/dist/doctor-lab.d.ts +7 -0
  35. package/dist/doctor-lab.js +26 -7
  36. package/dist/doctor-lab.js.map +1 -1
  37. package/dist/e2b-terminal-lab.js +3 -0
  38. package/dist/e2b-terminal-lab.js.map +1 -1
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/key-resolution.d.ts +1 -1
  43. package/dist/key-resolution.js +6 -4
  44. package/dist/key-resolution.js.map +1 -1
  45. package/dist/lab-config.d.ts +20 -3
  46. package/dist/lab-config.js +65 -3
  47. package/dist/lab-config.js.map +1 -1
  48. package/dist/lab-engine.js +8 -3
  49. package/dist/lab-engine.js.map +1 -1
  50. package/dist/lab-summary.d.ts +3 -2
  51. package/dist/lab-summary.js +17 -7
  52. package/dist/lab-summary.js.map +1 -1
  53. package/dist/observer-app.html +1 -1
  54. package/dist/observer-data.js +14 -1
  55. package/dist/observer-data.js.map +1 -1
  56. package/dist/oss-lab.d.ts +1 -1
  57. package/dist/oss-lab.js.map +1 -1
  58. package/dist/oss-meta-lab.d.ts +1 -1
  59. package/dist/oss-meta-lab.js.map +1 -1
  60. package/dist/program.d.ts +4 -0
  61. package/dist/program.js +231 -74
  62. package/dist/program.js.map +1 -1
  63. package/dist/run-narration-secrets.d.ts +5 -0
  64. package/dist/run-narration-secrets.js +68 -0
  65. package/dist/run-narration-secrets.js.map +1 -0
  66. package/dist/run.d.ts +7 -2
  67. package/dist/run.js +25 -3
  68. package/dist/run.js.map +1 -1
  69. package/dist/scripted-browser-lab.js +7 -0
  70. package/dist/scripted-browser-lab.js.map +1 -1
  71. package/dist/secret-prompt.d.ts +2 -0
  72. package/dist/secret-prompt.js +36 -0
  73. package/dist/secret-prompt.js.map +1 -0
  74. package/dist/shared-world-lab.js +4 -0
  75. package/dist/shared-world-lab.js.map +1 -1
  76. package/dist/study-analysis-engine.js +30 -1
  77. package/dist/study-analysis-engine.js.map +1 -1
  78. package/dist/study-analysis-evidence.js +9 -1
  79. package/dist/study-analysis-evidence.js.map +1 -1
  80. package/dist/tui-app.js +135 -135
  81. package/dist/tui-contract.d.ts +26 -1
  82. package/dist/tui-contract.js.map +1 -1
  83. package/dist/tui-launch.d.ts +2 -0
  84. package/dist/tui-launch.js +9 -1
  85. package/dist/tui-launch.js.map +1 -1
  86. package/docs/architecture/comms-inbox.md +4 -0
  87. package/docs/architecture/real-email-receiving.md +135 -0
  88. package/docs/contracts/run-bundle.md +9 -1
  89. package/docs/contracts/schemas.md +50 -5
  90. package/docs/goals/current.md +5 -5
  91. package/docs/ramp/README.md +13 -3
  92. package/docs/release/0.95.0-connections-setup.md +38 -0
  93. package/docs/release/0.96.0-real-email-receiving.md +47 -0
  94. package/package.json +4 -2
  95. package/skills/humanish/SKILL.md +55 -14
@@ -1,3 +1,7 @@
1
+ import { withTransientCommsSecrets } from "./run-narration-secrets.js";
2
+ import { prepareReceivingRun, receivingPublication } from "./comms-receiving-runtime.js";
3
+ import { deployReceivingInbox } from "./comms-receiving-inbox.js";
4
+ import { receivingEmailValidationReason } from "./lab-config.js";
1
5
  // The computer-use lab backend: a subject (an app-url the caller provisioned, or a repo the
2
6
  // lab clones AND serves in-sandbox) driven by a REGISTRY-RESOLVED computer-use actor inside a
3
7
  // hosted E2B desktop. This is the path that makes `actors[].type` load-bearing — the
@@ -237,7 +241,7 @@ export function composeLaneInstructions(args) {
237
241
  * runtime loopback/getHost address (not secret), so — mirroring the lobby-code runtime injection — this
238
242
  * augments only the instructions the model receives; the authored prompt + its digest are unchanged.
239
243
  * Returns a new spec (never mutates). Shared by the CUA + concurrent shared-world routes. */
240
- export function withInboxMission(spec, inboxUrl, address) {
244
+ export function withInboxMission(spec, inboxUrl, address, receiving = false) {
241
245
  // No assigned identity means no participant inbox; never fall back to the shared operator view.
242
246
  if (!address?.trim())
243
247
  return spec;
@@ -248,6 +252,11 @@ export function withInboxMission(spec, inboxUrl, address) {
248
252
  // and ends its session — the exact give-up class a live run documented — unless told the wait
249
253
  // is expected and the inbox is the next step.
250
254
  const identity = ` Your email address is ${address} — when the app asks for an email address, enter exactly that.`;
255
+ if (receiving)
256
+ return {
257
+ ...spec,
258
+ instructions: `${spec.instructions}\n\nEmail inbox:${identity} This is a fresh test identity; it does not replace an existing account's email address. When the app says it sent email, open ${inboxUrl} to check your inbox. Read the original email and use its verification link or code. Delivery may take a little time; refresh if needed. If mail remains missing or unavailable, report what you observed rather than assuming the app failed to send. The inbox may block remote images or undeclared destinations; those are harness limitations.`
259
+ };
251
260
  return {
252
261
  ...spec,
253
262
  instructions: `${spec.instructions}\n\nEmail inbox:${identity} When the app tells you it has emailed you (a verification link, confirmation code, or magic link), open ${recipientInboxUrl(inboxUrl, address)} in the browser to read that email and follow its link or enter its code. All email the app sends you arrives there. Waiting for an email is normal, not a blocker — do not end your session while waiting; open the inbox and refresh it until the email appears.`
@@ -1653,12 +1662,13 @@ export async function runCuaLane(spec, deps) {
1653
1662
  // in-sandbox catch (loopback) so its verification mail is CAPTURED, not sent to the internet. Gated
1654
1663
  // ENTIRELY on config.comms — no comms declared → zero change. The base-URL env is injected at
1655
1664
  // sandbox-create (below, so the app reads it at boot); the catch is started right after create.
1656
- const commsEmail = (cloneRoute || localTreeRoute) ? config.comms?.email : undefined;
1665
+ const commsEmail = (cloneRoute || localTreeRoute) && config.comms?.email?.kind === "fake" ? config.comms.email : undefined;
1657
1666
  const commsPort = commsEmail ? (commsEmail.port ?? DEFAULT_SANDBOX_CATCH_PORT) : undefined;
1658
1667
  // Hoisted so the finally can drain the catch before teardown; `commsArtifactPath` is the written
1659
1668
  // evidence path folded into the lane outcome.
1660
1669
  let deployedComms;
1661
1670
  let commsArtifactPath;
1671
+ let receivingInboxUrl;
1662
1672
  // injectEnv is absent on an adopter-hosted plane (#328): there is no subject env to inject
1663
1673
  // because the operator points their own app at their own catch.
1664
1674
  const commsEnv = commsEmail?.injectEnv !== undefined && commsPort !== undefined
@@ -1810,6 +1820,27 @@ export async function runCuaLane(spec, deps) {
1810
1820
  // Start the in-sandbox email catch BEFORE the subject serve, so the app's send-API base URL (injected
1811
1821
  // into its env at create) resolves the moment it boots. A comms-declared lab that can't stand the
1812
1822
  // catch up is a setup failure (fail closed) rather than silently sending real mail.
1823
+ if (deps.receiving) {
1824
+ const surface = await deployReceivingInbox(desktop, { leaseId: spec.streamId, requestTimeoutMs: Math.min(deps.requestTimeoutMs, 30_000) });
1825
+ receivingInboxUrl = surface.url;
1826
+ const email = config.comms?.email;
1827
+ try {
1828
+ await deps.receiving.attach(spec.laneId, {
1829
+ surface,
1830
+ allowedOrigins: [...new Set([new URL(targetUrl).origin, ...(email?.allowedOrigins ?? [])])],
1831
+ originMap: buildOriginMap({
1832
+ ...(config.subject.serve?.url === undefined ? {} : { internalServeUrl: config.subject.serve.url }),
1833
+ reachableBaseUrl: targetUrl,
1834
+ ...(email?.linkOrigin === undefined ? {} : { linkOrigin: email.linkOrigin })
1835
+ })
1836
+ });
1837
+ commsArtifactPath = "comms/receiving.json";
1838
+ }
1839
+ catch (error) {
1840
+ await surface.stop().catch(() => { });
1841
+ throw error;
1842
+ }
1843
+ }
1813
1844
  if (commsEmail && commsPort !== undefined) {
1814
1845
  deployedComms = await deployCommsCatch(desktop, {
1815
1846
  port: commsPort,
@@ -2098,11 +2129,13 @@ export async function runCuaLane(spec, deps) {
2098
2129
  // recipient it can actually receive mail into (else it would stall on an inbox that stays
2099
2130
  // empty). Two comms planes, mutually exclusive by parse: the in-sandbox catch humanish
2100
2131
  // deployed, or the adopter-hosted one (#380).
2101
- instructions: commsEmail && commsInboxUrl && deployedComms?.ready && laneHasInboxRecipient(commsEmail, spec.laneId)
2102
- ? withInboxMission(spec, commsInboxUrl, inboxRecipientFor(commsEmail, spec.laneId)?.address).instructions
2103
- : deps.externalComms && laneHasInboxRecipient(deps.externalComms.email, spec.laneId)
2104
- ? withInboxMission(spec, deps.externalComms.inboxUrl, inboxRecipientFor(deps.externalComms.email, spec.laneId)?.address).instructions
2105
- : spec.instructions,
2132
+ instructions: deps.receiving && receivingInboxUrl
2133
+ ? withInboxMission(spec, receivingInboxUrl, deps.receiving.address(spec.laneId), true).instructions
2134
+ : commsEmail && commsInboxUrl && deployedComms?.ready && laneHasInboxRecipient(commsEmail, spec.laneId)
2135
+ ? withInboxMission(spec, commsInboxUrl, inboxRecipientFor(commsEmail, spec.laneId)?.address).instructions
2136
+ : deps.externalComms && laneHasInboxRecipient(deps.externalComms.email, spec.laneId)
2137
+ ? withInboxMission(spec, deps.externalComms.inboxUrl, inboxRecipientFor(deps.externalComms.email, spec.laneId)?.address).instructions
2138
+ : spec.instructions,
2106
2139
  persona: spec.persona,
2107
2140
  timeoutMs: deps.timeoutMs,
2108
2141
  // The brain is either a keyed API client or a CLI the operator is already signed in to.
@@ -2257,6 +2290,14 @@ export async function runCuaLane(spec, deps) {
2257
2290
  : { warnings: [...(desktopGeometry.warnings ?? []), ...geometryWarnings] })
2258
2291
  };
2259
2292
  }
2293
+ if (deps.receiving) {
2294
+ try {
2295
+ await deps.receiving.finishParticipant(spec.laneId);
2296
+ }
2297
+ catch {
2298
+ warnings.push("Real email finalization is incomplete. Inspect communication cleanup with humanish comms recover.");
2299
+ }
2300
+ }
2260
2301
  // Off-app comms evidence (#297): before this lane's sandbox is torn down, drain everything the
2261
2302
  // in-sandbox catch captured, route it into a host fake inbox addressed to the declared
2262
2303
  // recipients, and write the digest-only thread artifact. Wrapped so a drain failure NEVER
@@ -2700,6 +2741,9 @@ function subjectProvenanceArg(subject, publicRepo, subjectEnvNames) {
2700
2741
  * ticking into a directory something else is deleting, which surfaces as an unrelated ENOTEMPTY.
2701
2742
  */
2702
2743
  export async function runCuaActorLab(options) {
2744
+ return withTransientCommsSecrets(() => runCuaActorLabWithSecrets(options));
2745
+ }
2746
+ async function runCuaActorLabWithSecrets(options) {
2703
2747
  const analysisReason = resolveAutomaticAnalysis(options.config.review?.analysis);
2704
2748
  const tasksReason = analysisReason.ok ? taskProtocolValidationReason(options.config, true) : analysisReason.message;
2705
2749
  if (tasksReason)
@@ -2792,7 +2836,12 @@ async function runCuaActorLabInScope(options) {
2792
2836
  if (actor?.maxOutputTokens !== undefined && (hooks.runSession || hooks.buildProvider || hooks.buildExecutor)) {
2793
2837
  return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "maxOutputTokens cannot be enforced by a custom runSession/provider/executor route.", descriptor.id);
2794
2838
  }
2839
+ const receivingReason = receivingEmailValidationReason(config);
2840
+ if (receivingReason)
2841
+ return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", receivingReason, descriptor.id);
2795
2842
  const inProcessRoute = hooks.buildExecutor !== undefined;
2843
+ if (inProcessRoute && config.comms?.email?.kind === "real")
2844
+ return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "Real email receiving requires hosted participant desktops.", descriptor.id);
2796
2845
  if (inProcessRoute && config.execution?.desktop?.media !== undefined) {
2797
2846
  return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "execution.desktop.media is not provisioned by a caller-supplied executor. Remove the declaration or use a hosted computer-use browser lane.", descriptor.id);
2798
2847
  }
@@ -3267,20 +3316,47 @@ async function runCuaActorLabInScope(options) {
3267
3316
  await flushWriting;
3268
3317
  };
3269
3318
  }
3319
+ const receivingWarnings = [];
3320
+ let receiving;
3321
+ if (!dryRun && config.comms?.email?.kind === "real") {
3322
+ try {
3323
+ receiving = await prepareReceivingRun({ cwd, runId, config, env, participants: laneSpecs.map(spec => spec.laneId), runPaths,
3324
+ registerSecrets: values => { for (const value of values)
3325
+ if (value.length >= 4 && !knownSecretValues.includes(value))
3326
+ knownSecretValues.push(value); }
3327
+ });
3328
+ if (receiving)
3329
+ deps.receiving = receiving;
3330
+ }
3331
+ catch {
3332
+ await stopLiveFlush?.();
3333
+ return fail("HUMANISH_CUA_LAB_SUBJECT_INVALID", "Real email setup failed before desktop allocation. Run humanish comms check --online and humanish comms recover to inspect authentication and pending cleanup.", descriptor.id);
3334
+ }
3335
+ }
3270
3336
  // Run lanes (dry-run runs none). In-process is always one lane.
3271
3337
  let outcomes;
3272
3338
  let failFastReason;
3273
- if (!dryRun) {
3274
- if (inProcessRoute) {
3275
- outcomes = [await runInProcessLane(laneSpecs[0], deps)];
3339
+ try {
3340
+ if (!dryRun) {
3341
+ if (inProcessRoute) {
3342
+ outcomes = [await runInProcessLane(laneSpecs[0], deps)];
3343
+ }
3344
+ else if (laneCount === 1) {
3345
+ outcomes = [await runCuaLane(laneSpecs[0], deps)];
3346
+ }
3347
+ else {
3348
+ const ran = await runCuaLanes(laneSpecs, deps, plan.concurrency);
3349
+ outcomes = ran.outcomes;
3350
+ failFastReason = ran.failFastReason;
3351
+ }
3276
3352
  }
3277
- else if (laneCount === 1) {
3278
- outcomes = [await runCuaLane(laneSpecs[0], deps)];
3353
+ }
3354
+ finally {
3355
+ try {
3356
+ await receiving?.finish();
3279
3357
  }
3280
- else {
3281
- const ran = await runCuaLanes(laneSpecs, deps, plan.concurrency);
3282
- outcomes = ran.outcomes;
3283
- failFastReason = ran.failFastReason;
3358
+ catch {
3359
+ receivingWarnings.push("Email finalization could not complete. Inspect humanish comms recover; provider cleanup remains unresolved.");
3284
3360
  }
3285
3361
  }
3286
3362
  // Close the live flush BEFORE any final artifact work: no new flush may start, and an
@@ -3438,6 +3514,8 @@ async function runCuaActorLabInScope(options) {
3438
3514
  hookLabel: "cuaHooks",
3439
3515
  ...(options.scorerProvenance === undefined ? {} : { scorerProvenance: options.scorerProvenance })
3440
3516
  });
3517
+ if (receiving)
3518
+ bundle.commsReceiving = receiving.snapshot();
3441
3519
  await writeCuaRunArtifacts(bundle, createdAt, runPaths);
3442
3520
  // Finalize the status record from the bundle that was just written, so the index can never
3443
3521
  // claim an outcome the evidence does not carry. A run that throws before reaching here leaves
@@ -3467,7 +3545,7 @@ async function runCuaActorLabInScope(options) {
3467
3545
  const adapterFailure = adapterScoreFailureMessage(bundle);
3468
3546
  const ok = observer.ok && allLanesOk && adapterFailure === undefined && scorerResult.declaredVerdictFailure === undefined;
3469
3547
  const laneWarnings = (outcomes ?? []).flatMap((outcome) => outcome.warnings);
3470
- const warnings = [...laneWarnings, ...aggregateWarnings, ...adapterWarnings, ...observer.warnings];
3548
+ const warnings = [...receivingWarnings, ...laneWarnings, ...aggregateWarnings, ...adapterWarnings, ...observer.warnings];
3471
3549
  const laneResults = laneSpecs.map((spec, index) => toLaneResult(spec, outcomes?.[index], laneSubjects[index], dryRun));
3472
3550
  const laneSummary = buildLaneSummary(outcomes, laneCount, plan, dryRun);
3473
3551
  const firstOutcome = outcomes?.[0];
@@ -3625,6 +3703,7 @@ function observerResultForCuaArtifacts(cwd, runId, artifactRoot, warnings = [])
3625
3703
  function buildSingleLaneBundle(args) {
3626
3704
  const { spec, outcome, config } = args;
3627
3705
  return buildCuaBundle({
3706
+ realEmail: config.comms?.email?.kind === "real",
3628
3707
  ...(args.lab === undefined ? {} : { lab: args.lab }),
3629
3708
  actorId: args.descriptor.id,
3630
3709
  appUrl: args.appUrl,
@@ -4520,6 +4599,7 @@ export function buildCuaBundle(args) {
4520
4599
  }, [stream]);
4521
4600
  return {
4522
4601
  schema: RUN_BUNDLE_SCHEMA,
4602
+ ...(args.realEmail && !args.dryRun ? { publication: { restrictions: ["real-communications"] } } : {}),
4523
4603
  runId: args.runId,
4524
4604
  mode: args.dryRun ? "dry-run" : "live",
4525
4605
  simCount: 1,
@@ -5008,6 +5088,7 @@ export function buildCuaFanoutBundle(args) {
5008
5088
  const cost = buildCuaCostSummary({ lanes: costLanes, desktops });
5009
5089
  return {
5010
5090
  schema: RUN_BUNDLE_SCHEMA,
5091
+ ...receivingPublication(args.config, args.dryRun),
5011
5092
  runId: args.runId,
5012
5093
  mode: args.dryRun ? "dry-run" : "live",
5013
5094
  simCount: specs.length,