humanish 0.37.0 → 0.39.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.
@@ -40,6 +40,8 @@ import { mapWithConcurrency } from "./concurrency.js";
40
40
  import { appendSandboxReceipt } from "./sandbox-receipts.js";
41
41
  import { assertScreenshotEvidence } from "./image-evidence.js";
42
42
  import { buildObserverData } from "./observer-data.js";
43
+ import { personaToDirectives, renderPersonaPromptSection } from "./persona.js";
44
+ import { labPersonaIds, resolveCommittedPersonas } from "./persona-resolve.js";
43
45
  import { attachObserverRuntimeStreamUrls, renderObserver } from "./observer.js";
44
46
  import { containsSensitive, digestText, redactedTail, redactText } from "./redaction.js";
45
47
  import { assertPreparedSelectedOutputDirectory, assertSafeOutputPathSegment, prepareContainedOutputDirectory, prepareSelectedOutputDirectory, writeContainedOutputFile, writePreparedRunLatestPointer } from "./selected-output-paths.js";
@@ -94,8 +96,15 @@ export function composeLaneInstructions(args) {
94
96
  const deviceLine = preset.isMobile
95
97
  ? `You are a mobile user on a ${name} device (${preset.width}x${preset.height} @${preset.deviceScaleFactor}x). Expect a mobile/touch layout.`
96
98
  : `You are a desktop user (${name}, ${preset.width}x${preset.height}).`;
99
+ // A resolved persona contributes its compiled directives (friction tolerance, skill bias,
100
+ // accessibility behavior, constraints) through the SAME persona.ts compiler the terminal lane
101
+ // uses, so one persona file means one behavior across every route.
102
+ const personaLine = args.resolvedPersona
103
+ ? renderPersonaPromptSection(args.resolvedPersona)
104
+ : args.persona ? `Persona: ${args.persona}.` : undefined;
105
+ const traitsApplied = args.resolvedPersona ? personaToDirectives(args.resolvedPersona).traitsApplied : [];
97
106
  const parts = [
98
- args.persona ? `Persona: ${args.persona}.` : undefined,
107
+ personaLine,
99
108
  deviceLine,
100
109
  args.mission,
101
110
  args.instruction ? `Lane focus: ${args.instruction}` : undefined
@@ -105,7 +114,7 @@ export function composeLaneInstructions(args) {
105
114
  instructions,
106
115
  persona: {
107
116
  id: args.persona ?? "cua-operator",
108
- traitsApplied: [],
117
+ traitsApplied,
109
118
  promptDigest: digestText(instructions, 16)
110
119
  }
111
120
  };
@@ -239,9 +248,12 @@ function laneSpecsAndPlan(config, opts = {}) {
239
248
  const simId = `sim-${String(i + 1).padStart(3, "0")}`;
240
249
  const streamId = `stream-${String(i + 1).padStart(3, "0")}`;
241
250
  const device = resolveLaneDevice(config, lane);
251
+ const personaId = (roster ? lane?.persona : actor?.persona);
252
+ const resolvedPersona = personaId === undefined ? undefined : opts.personas?.get(personaId);
242
253
  const composed = composeLaneInstructions({
243
254
  mission,
244
- ...(((roster ? lane?.persona : actor?.persona)) === undefined ? {} : { persona: (roster ? lane?.persona : actor?.persona) }),
255
+ ...(personaId === undefined ? {} : { persona: personaId }),
256
+ ...(resolvedPersona === undefined ? {} : { resolvedPersona }),
245
257
  ...(((roster ? lane?.instruction : actor?.laneFocus?.instruction)) === undefined ? {} : { instruction: (roster ? lane?.instruction : actor?.laneFocus?.instruction) }),
246
258
  device: { name: device.name, preset: device.preset }
247
259
  });
@@ -1061,7 +1073,9 @@ export async function runCuaLane(spec, deps) {
1061
1073
  // evidence path folded into the lane outcome.
1062
1074
  let deployedComms;
1063
1075
  let commsArtifactPath;
1064
- const commsEnv = commsEmail && commsPort !== undefined
1076
+ // injectEnv is absent on an adopter-hosted plane (#328): there is no subject env to inject
1077
+ // because the operator points their own app at their own catch.
1078
+ const commsEnv = commsEmail?.injectEnv !== undefined && commsPort !== undefined
1065
1079
  ? { [commsEmail.injectEnv]: `http://127.0.0.1:${commsPort}` }
1066
1080
  : {};
1067
1081
  // Persona inbox SURFACE (#297 slice B): the loopback URL the persona opens to read captured mail; the
@@ -1963,11 +1977,18 @@ export async function runCuaActorLab(options) {
1963
1977
  if (fanoutReason) {
1964
1978
  return fail("HUMANISH_CUA_LAB_FANOUT_INVALID", fanoutReason, descriptor.id);
1965
1979
  }
1980
+ // Compile any committed personas BEFORE planning, so the plan builder stays pure and each lane's
1981
+ // prompt carries real behavioral directives rather than a bare `Persona: <id>.` label (#381).
1982
+ const personaResolution = await resolveCommittedPersonas(projectRoot, labPersonaIds(config));
1983
+ for (const warning of personaResolution.warnings) {
1984
+ process.stderr.write(`humanish: ${warning}\n`);
1985
+ }
1966
1986
  // Resolve the lane plan (pure) — the SAME table for dry-run and live.
1967
1987
  let { lanes: laneSpecs, plan } = laneSpecsAndPlan(config, {
1968
1988
  ...(options.countOverride === undefined ? {} : { countOverride: options.countOverride }),
1969
1989
  env,
1970
- dryRun
1990
+ dryRun,
1991
+ personas: personaResolution.personas
1971
1992
  });
1972
1993
  let laneCount = laneSpecs.length;
1973
1994
  if (laneCount > MAX_CUA_LANES) {