humanish 0.15.0 → 0.15.2

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 (72) hide show
  1. package/README.md +33 -31
  2. package/dist/claude-agent-sdk.js +14 -7
  3. package/dist/claude-agent-sdk.js.map +1 -1
  4. package/dist/codex-app-server-ui.d.ts +1 -1
  5. package/dist/codex-app-server-ui.js +69 -39
  6. package/dist/codex-app-server-ui.js.map +1 -1
  7. package/dist/codex-app-server.d.ts +3 -0
  8. package/dist/codex-app-server.js +15 -8
  9. package/dist/codex-app-server.js.map +1 -1
  10. package/dist/concurrent-shared-world-lab.js +18 -16
  11. package/dist/concurrent-shared-world-lab.js.map +1 -1
  12. package/dist/core/git-state.d.ts +2 -0
  13. package/dist/core/git-state.js +152 -17
  14. package/dist/core/git-state.js.map +1 -1
  15. package/dist/core/git-workspace.d.ts +31 -0
  16. package/dist/core/git-workspace.js +447 -0
  17. package/dist/core/git-workspace.js.map +1 -0
  18. package/dist/cua-actor-lab.d.ts +3 -2
  19. package/dist/cua-actor-lab.js +34 -23
  20. package/dist/cua-actor-lab.js.map +1 -1
  21. package/dist/e2b-terminal-lab.js +111 -46
  22. package/dist/e2b-terminal-lab.js.map +1 -1
  23. package/dist/feedback.js +139 -101
  24. package/dist/feedback.js.map +1 -1
  25. package/dist/init.d.ts +1 -1
  26. package/dist/init.js +108 -22
  27. package/dist/init.js.map +1 -1
  28. package/dist/lab-config.d.ts +5 -0
  29. package/dist/lab-config.js +38 -0
  30. package/dist/lab-config.js.map +1 -1
  31. package/dist/labs.js +258 -52
  32. package/dist/labs.js.map +1 -1
  33. package/dist/observer-assets.js +3 -2
  34. package/dist/observer-assets.js.map +1 -1
  35. package/dist/observer-static.js +131 -17
  36. package/dist/observer-static.js.map +1 -1
  37. package/dist/observer.js +263 -66
  38. package/dist/observer.js.map +1 -1
  39. package/dist/openai-responses-cu.js +18 -4
  40. package/dist/openai-responses-cu.js.map +1 -1
  41. package/dist/oss-lab.js +59 -14
  42. package/dist/oss-lab.js.map +1 -1
  43. package/dist/oss-meta-lab.d.ts +2 -4
  44. package/dist/oss-meta-lab.js +117 -477
  45. package/dist/oss-meta-lab.js.map +1 -1
  46. package/dist/program.js +23 -21
  47. package/dist/program.js.map +1 -1
  48. package/dist/run-paths.d.ts +56 -0
  49. package/dist/run-paths.js +340 -0
  50. package/dist/run-paths.js.map +1 -0
  51. package/dist/run.d.ts +14 -4
  52. package/dist/run.js +595 -306
  53. package/dist/run.js.map +1 -1
  54. package/dist/scripted-browser-actor.d.ts +7 -4
  55. package/dist/scripted-browser-actor.js +154 -41
  56. package/dist/scripted-browser-actor.js.map +1 -1
  57. package/dist/scripted-browser-lab.js +95 -45
  58. package/dist/scripted-browser-lab.js.map +1 -1
  59. package/dist/selected-output-paths.d.ts +49 -0
  60. package/dist/selected-output-paths.js +354 -0
  61. package/dist/selected-output-paths.js.map +1 -0
  62. package/dist/shared-world-lab.js +13 -12
  63. package/dist/shared-world-lab.js.map +1 -1
  64. package/dist/source-archive.js +44 -1
  65. package/dist/source-archive.js.map +1 -1
  66. package/docs/architecture/oss-lab-poc.md +8 -0
  67. package/docs/assets/humanish-observer-hero.png +0 -0
  68. package/docs/contracts/policy.md +7 -1
  69. package/docs/goals/current.md +20 -1
  70. package/docs/product/open-source-install-experience.md +9 -1
  71. package/docs/ramp/README.md +14 -7
  72. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { randomBytes } from "node:crypto";
3
- import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
3
+ import { lstat, mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { runDesktopCommandOrThrow } from "./command-failure.js";
6
6
  import { promisify } from "node:util";
@@ -9,6 +9,8 @@ import { attachObserverRuntimeStreamUrls, renderObserver } from "./observer.js";
9
9
  import { loadE2BDesktopModule } from "./e2b-desktop-launch.js";
10
10
  import { DEFAULT_OSS_REPOS, normalizeOssRepoSlugs, repoSlug, validateOssRepoSlug } from "./oss-lab.js";
11
11
  import { redactOssRemoteTelemetryText } from "./oss-remote-telemetry.js";
12
+ import { bindExistingRunArtifactPaths, prepareExclusiveHumanishStorageDirectory, prepareReusableHumanishStorageDirectory, validatePreparedRunArtifactPaths } from "./run-paths.js";
13
+ import { writeContainedOutputFile } from "./selected-output-paths.js";
12
14
  import { buildRunSource, PUBLIC_TARGET_CWD, REVIEW_SCHEMA, RUN_BUNDLE_SCHEMA, runDryRun } from "./run.js";
13
15
  import { scoreOssMetaMeaningfulUse } from "./oss-meta-lab-scoring.js";
14
16
  export const OSS_META_LAB_SCHEMA = "humanish.oss-meta-lab-result.v1";
@@ -55,35 +57,9 @@ export function collectOssMetaLabRemoteEnv(env) {
55
57
  }
56
58
  return result;
57
59
  }
58
- export function collectOssMetaLabPrivateEnv(env) {
59
- const result = {};
60
- const codexApiKey = env.CODEX_API_KEY?.trim() || env.OPENAI_API_KEY?.trim();
61
- const codexAccessToken = env.CODEX_ACCESS_TOKEN?.trim();
62
- const codexAppServerUrl = env.HUMANISH_OSS_META_CODEX_APP_SERVER_URL?.trim()
63
- || env.CODEX_APP_SERVER_CLIENT_URL?.trim()
64
- || env.CODEX_APP_SERVER_URL?.trim();
65
- const githubToken = githubTokenFromEnv(env);
66
- if (codexApiKey) {
67
- result.HUMANISH_CODEX_API_KEY = codexApiKey;
68
- }
69
- if (codexAccessToken) {
70
- result.HUMANISH_CODEX_ACCESS_TOKEN = codexAccessToken;
71
- }
72
- if (codexAppServerUrl) {
73
- result.HUMANISH_CODEX_APP_SERVER_URL = codexAppServerUrl;
74
- }
75
- if (githubToken) {
76
- result.HUMANISH_GITHUB_TOKEN = githubToken;
77
- }
78
- return result;
79
- }
80
60
  function githubTokenFromEnv(env) {
81
61
  return env.GH_TOKEN?.trim() || env.GITHUB_TOKEN?.trim() || env.GITHUB_PAT?.trim() || "";
82
62
  }
83
- async function withGitHubAskPassEnv(root, env, callback) {
84
- const gitEnv = await createGitHubAskPassEnv(root, env);
85
- return callback(gitEnv);
86
- }
87
63
  async function createGitHubAskPassEnv(root, env) {
88
64
  await mkdir(root, { recursive: true });
89
65
  const askPassPath = path.join(root, `git-askpass-${randomBytes(4).toString("hex")}.sh`);
@@ -143,17 +119,19 @@ async function runGitRepoAccessProbe(execImpl, cwd, repoUrl, env, sourceEnv) {
143
119
  export async function preflightOssMetaRepoAccess(args) {
144
120
  const execImpl = args.execFileImpl ?? execFileAsync;
145
121
  const tokenPresent = Boolean(githubTokenFromEnv(args.env));
146
- const root = path.join(args.cwd, ".humanish", "tmp", `repo-access-${randomBytes(4).toString("hex")}`);
122
+ const rootId = `repo-access-${randomBytes(4).toString("hex")}`;
123
+ const preparedRoot = await prepareExclusiveHumanishStorageDirectory(args.cwd, "tmp", rootId);
124
+ const root = await pinCleanupDirectory(preparedRoot);
147
125
  try {
148
- await mkdir(root, { recursive: true });
149
- const tokenGitEnv = tokenPresent ? await createGitHubAskPassEnv(root, args.env) : undefined;
126
+ await mkdir(root.physicalPath, { recursive: true });
127
+ const tokenGitEnv = tokenPresent ? await createGitHubAskPassEnv(root.physicalPath, args.env) : undefined;
150
128
  const anonymousGitEnv = gitEnvWithoutGitHubToken(args.env);
151
129
  const results = [];
152
130
  for (const assignment of args.assignments) {
153
131
  const repoUrl = `https://github.com/${assignment.repo}.git`;
154
132
  let anonymousError;
155
133
  try {
156
- await runGitRepoAccessProbe(execImpl, root, repoUrl, anonymousGitEnv, args.env);
134
+ await runGitRepoAccessProbe(execImpl, root.physicalPath, repoUrl, anonymousGitEnv, args.env);
157
135
  results.push({
158
136
  ok: true,
159
137
  reason: tokenPresent
@@ -171,7 +149,7 @@ export async function preflightOssMetaRepoAccess(args) {
171
149
  let tokenError;
172
150
  if (tokenGitEnv) {
173
151
  try {
174
- await runGitRepoAccessProbe(execImpl, root, repoUrl, tokenGitEnv, args.env);
152
+ await runGitRepoAccessProbe(execImpl, root.physicalPath, repoUrl, tokenGitEnv, args.env);
175
153
  results.push({
176
154
  ok: true,
177
155
  reason: "GitHub repo clone access preflight passed with token auth after anonymous clone access failed.",
@@ -202,9 +180,28 @@ export async function preflightOssMetaRepoAccess(args) {
202
180
  return results;
203
181
  }
204
182
  finally {
205
- await rm(root, { recursive: true, force: true }).catch(() => undefined);
183
+ if (await validatePinnedCleanupDirectory(root).catch(() => false)) {
184
+ await rm(root.physicalPath, { recursive: true, force: true }).catch(() => undefined);
185
+ }
206
186
  }
207
187
  }
188
+ async function pinCleanupDirectory(directoryInput) {
189
+ const physicalPath = await realpath(path.resolve(directoryInput));
190
+ const stats = await lstat(physicalPath, { bigint: true });
191
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
192
+ throw new Error("OSS meta-lab cleanup roots must be physical directories.");
193
+ }
194
+ return Object.freeze({ birthtimeNs: stats.birthtimeNs, dev: stats.dev, ino: stats.ino, physicalPath });
195
+ }
196
+ async function validatePinnedCleanupDirectory(directory) {
197
+ const stats = await lstat(directory.physicalPath, { bigint: true });
198
+ return !stats.isSymbolicLink()
199
+ && stats.isDirectory()
200
+ && stats.birthtimeNs === directory.birthtimeNs
201
+ && stats.dev === directory.dev
202
+ && stats.ino === directory.ino
203
+ && await realpath(directory.physicalPath) === directory.physicalPath;
204
+ }
208
205
  export async function preflightOssMetaActorApiKey(args) {
209
206
  const apiKey = args.env.CODEX_API_KEY?.trim() || args.env.OPENAI_API_KEY?.trim();
210
207
  if (!apiKey) {
@@ -266,163 +263,8 @@ function remoteActorAuthRequested(env) {
266
263
  function actorRequired(env) {
267
264
  return env.HUMANISH_OSS_META_REQUIRE_ACTOR === "1";
268
265
  }
269
- async function createHostActorPlans(args) {
270
- return Promise.all(args.assignments.map((assignment) => createHostActorPlan({
271
- assignment,
272
- cwd: args.cwd,
273
- redactRepoNames: args.redactRepoNames,
274
- runId: args.runId
275
- })));
276
- }
277
- async function createHostActorPlan(args) {
278
- const token = repoSlug(args.assignment.repo);
279
- const actorRoot = path.join(args.cwd, ".humanish", "runs", args.runId, "host-actors", token);
280
- const artifactPath = path.join("host-actors", token, "actor-plan.json");
281
- const tmpRoot = path.join(args.cwd, ".humanish", "tmp", "host-actors", args.runId, token);
282
- const repoDir = path.join(tmpRoot, "repo");
283
- const planPath = path.join(actorRoot, "actor-plan.json");
284
- const schemaPath = path.join(tmpRoot, "actor-plan.schema.json");
285
- await mkdir(actorRoot, { recursive: true });
286
- if (args.redactRepoNames) {
287
- const plan = failedHostActorPlan({
288
- repo: "[redacted-authorized-repo]",
289
- status: "blocked",
290
- summary: "Host Codex actor plans are public-safe artifacts and require non-redacted public repo labels."
291
- });
292
- await writeJson(planPath, plan);
293
- return {
294
- artifactPath,
295
- error: plan.summary,
296
- plan,
297
- planPath,
298
- repo: args.assignment.repo,
299
- streamId: args.assignment.streamId,
300
- worktreePath: repoDir
301
- };
302
- }
303
- try {
304
- await mkdir(tmpRoot, { recursive: true });
305
- await withGitHubAskPassEnv(tmpRoot, process.env, async (gitEnv) => {
306
- await execFileAsync("git", ["clone", "--depth=1", `https://github.com/${args.assignment.repo}.git`, repoDir], {
307
- cwd: tmpRoot,
308
- env: gitEnv,
309
- maxBuffer: 10 * 1024 * 1024,
310
- timeout: readPositiveInt(process.env.HUMANISH_OSS_META_HOST_CLONE_TIMEOUT_MS, 90_000)
311
- });
312
- });
313
- await writeJson(schemaPath, hostActorPlanJsonSchema());
314
- const repoContext = await readHostActorRepoContext(repoDir);
315
- const outputPath = path.join(tmpRoot, "codex-last-message.json");
316
- const codexEnv = hostCodexEnv(process.env);
317
- const codexCommand = [
318
- "codex exec",
319
- "--ephemeral",
320
- "--ignore-user-config",
321
- "--skip-git-repo-check",
322
- "--dangerously-bypass-approvals-and-sandbox",
323
- "-C",
324
- shellQuote(repoDir),
325
- "--output-schema",
326
- shellQuote(schemaPath),
327
- "--output-last-message",
328
- shellQuote(outputPath),
329
- shellQuote(buildHostActorPrompt(args.assignment.repo, repoContext)),
330
- "< /dev/null"
331
- ].join(" ");
332
- const codexResult = await execFileAsync("bash", ["-lc", codexCommand], {
333
- cwd: repoDir,
334
- env: codexEnv,
335
- maxBuffer: 10 * 1024 * 1024,
336
- timeout: readPositiveInt(process.env.HUMANISH_OSS_META_HOST_ACTOR_TIMEOUT_MS, 240_000)
337
- });
338
- const rawPlan = await readFile(outputPath, "utf8").catch(() => {
339
- const stdout = typeof codexResult.stdout === "string" ? codexResult.stdout : "";
340
- const stderr = typeof codexResult.stderr === "string" ? codexResult.stderr : "";
341
- return [stdout, stderr].filter((value) => value.trim()).join("\n");
342
- });
343
- await writeFile(path.join(actorRoot, "codex-output.txt"), `${sanitizeRemoteLog(rawPlan)}\n`, "utf8");
344
- const plan = normalizeHostActorPlan(rawPlan, args.assignment.repo);
345
- await writeJson(planPath, plan);
346
- return {
347
- artifactPath,
348
- ...(plan.status === "passed" ? {} : { error: plan.summary }),
349
- plan,
350
- planPath,
351
- repo: args.assignment.repo,
352
- streamId: args.assignment.streamId,
353
- worktreePath: repoDir
354
- };
355
- }
356
- catch (error) {
357
- const plan = failedHostActorPlan({
358
- repo: args.assignment.repo,
359
- status: "failed",
360
- summary: compactError(error)
361
- });
362
- await writeJson(planPath, plan);
363
- return {
364
- artifactPath,
365
- error: plan.summary,
366
- plan,
367
- planPath,
368
- repo: args.assignment.repo,
369
- streamId: args.assignment.streamId,
370
- worktreePath: repoDir
371
- };
372
- }
373
- finally {
374
- await rm(tmpRoot, { recursive: true, force: true }).catch(() => undefined);
375
- }
376
- }
377
- function hostCodexEnv(env) {
378
- const allowed = [
379
- "CODEX_HOME",
380
- "HOME",
381
- "LANG",
382
- "LC_ALL",
383
- "LC_CTYPE",
384
- "LOGNAME",
385
- "PATH",
386
- "SHELL",
387
- "TERM",
388
- "TMP",
389
- "TMPDIR",
390
- "TEMP",
391
- "USER",
392
- "XDG_CACHE_HOME",
393
- "XDG_CONFIG_HOME"
394
- ];
395
- return Object.fromEntries(allowed.flatMap((name) => {
396
- const value = env[name];
397
- return value === undefined ? [] : [[name, value]];
398
- }));
399
- }
400
- async function readHostActorRepoContext(repoDir) {
401
- const packageText = await readFile(path.join(repoDir, "package.json"), "utf8").catch(() => "");
402
- const readmeText = await readFile(path.join(repoDir, "README.md"), "utf8").catch(() => "");
403
- const indexText = await readFile(path.join(repoDir, "index.html"), "utf8").catch(() => "");
404
- let packageSummary = "package.json missing";
405
- try {
406
- const pkg = JSON.parse(packageText);
407
- packageSummary = JSON.stringify({
408
- name: pkg.name,
409
- packageManager: pkg.packageManager,
410
- scripts: pkg.scripts ?? {},
411
- dependencies: Object.keys(pkg.dependencies ?? {}).slice(0, 20),
412
- devDependencies: Object.keys(pkg.devDependencies ?? {}).slice(0, 20)
413
- }, null, 2);
414
- }
415
- catch { }
416
- return [
417
- "package_summary:",
418
- packageSummary.slice(0, 2_500),
419
- "",
420
- "readme_excerpt:",
421
- readmeText.replace(/\s+/g, " ").trim().slice(0, 2_000) || "(missing)",
422
- "",
423
- "index_excerpt:",
424
- indexText.replace(/\s+/g, " ").trim().slice(0, 800) || "(missing)"
425
- ].join("\n");
266
+ async function createHostActorPlans(_args) {
267
+ throw new Error("Host actor planning is disabled until live OSS meta-lab execution has an isolated credential boundary.");
426
268
  }
427
269
  function repoAccessFailureReason(args) {
428
270
  const anonymous = sanitizeRepoAccessError(args.anonymousError, args.repo, args.redactRepoName);
@@ -477,148 +319,6 @@ function blockedLiveDesktopsForRepoAccess(args) {
477
319
  };
478
320
  });
479
321
  }
480
- function buildHostActorPrompt(repo, repoContext) {
481
- return [
482
- "You are a public-safe Humanish host actor.",
483
- "Use the bounded public repository context below to author a compact Humanish setup plan.",
484
- "Do not print secrets, environment values, private data, or long source snippets.",
485
- "Do not commit, push, file issues, or mutate remotes.",
486
- "Return only JSON matching the supplied schema.",
487
- "",
488
- `Repository: ${repo}`,
489
- "",
490
- "Plan requirements:",
491
- "- status must be passed if you can infer useful public-safe personas/scenarios.",
492
- "- Include exactly 1 or 2 synthetic personas.",
493
- "- Include exactly 1 or 2 desktop/mobile browser scenarios.",
494
- "- Scenario steps must be concise and public-safe.",
495
- "- recommendedProof should name the strongest Humanish command shape for this repo.",
496
- "- Current Humanish supports `humanish run --app-url <loopback-url> --sims 2`; do not invent --browser, --viewport, --persona, or --scenario flags.",
497
- "",
498
- "Bounded public repo context:",
499
- repoContext
500
- ].join("\n");
501
- }
502
- function hostActorPlanJsonSchema() {
503
- return {
504
- type: "object",
505
- additionalProperties: false,
506
- required: ["status", "summary", "personas", "scenarios", "recommendedProof"],
507
- properties: {
508
- status: { type: "string", enum: ["passed", "blocked", "failed"] },
509
- summary: { type: "string" },
510
- personas: {
511
- type: "array",
512
- minItems: 1,
513
- maxItems: 2,
514
- items: {
515
- type: "object",
516
- additionalProperties: false,
517
- required: ["id", "name", "intent", "traits"],
518
- properties: {
519
- id: { type: "string" },
520
- name: { type: "string" },
521
- intent: { type: "string" },
522
- traits: {
523
- type: "array",
524
- minItems: 1,
525
- maxItems: 5,
526
- items: { type: "string" }
527
- }
528
- }
529
- }
530
- },
531
- recommendedProof: { type: "string" },
532
- scenarios: {
533
- type: "array",
534
- minItems: 1,
535
- maxItems: 2,
536
- items: {
537
- type: "object",
538
- additionalProperties: false,
539
- required: ["id", "title", "goal", "steps"],
540
- properties: {
541
- id: { type: "string" },
542
- title: { type: "string" },
543
- goal: { type: "string" },
544
- steps: {
545
- type: "array",
546
- minItems: 2,
547
- maxItems: 8,
548
- items: { type: "string" }
549
- }
550
- }
551
- }
552
- }
553
- }
554
- };
555
- }
556
- function normalizeHostActorPlan(raw, repo) {
557
- const parsed = parseJsonObject(raw);
558
- if (!parsed) {
559
- return failedHostActorPlan({
560
- repo,
561
- status: "failed",
562
- summary: "Host Codex actor did not return parseable JSON."
563
- });
564
- }
565
- const personas = Array.isArray(parsed.personas)
566
- ? parsed.personas.map(normalizeHostActorPersona).filter((persona) => persona !== null).slice(0, 2)
567
- : [];
568
- const scenarios = Array.isArray(parsed.scenarios)
569
- ? parsed.scenarios.map(normalizeHostActorScenario).filter((scenario) => scenario !== null).slice(0, 2)
570
- : [];
571
- const status = parsed.status === "blocked" || parsed.status === "failed" ? parsed.status : "passed";
572
- if (status === "passed" && (personas.length === 0 || scenarios.length === 0)) {
573
- return failedHostActorPlan({
574
- repo,
575
- status: "failed",
576
- summary: "Host Codex actor plan lacked usable personas or scenarios."
577
- });
578
- }
579
- return {
580
- schema: "humanish.oss-host-actor-plan.v1",
581
- generatedAt: new Date().toISOString(),
582
- personas,
583
- recommendedProof: normalizeHostActorRecommendedProof(parsed.recommendedProof),
584
- repo,
585
- scenarios,
586
- source: "local-codex-exec",
587
- status,
588
- summary: cleanHostActorText(parsed.summary, status === "passed" ? "Host Codex actor authored a public-safe Humanish plan." : "Host Codex actor could not author a complete plan.")
589
- };
590
- }
591
- function normalizeHostActorPersona(value) {
592
- if (!value || typeof value !== "object")
593
- return null;
594
- const candidate = value;
595
- const id = safeArtifactToken(cleanHostActorText(candidate.id, "host-actor-persona")).slice(0, 80) || "host-actor-persona";
596
- const traits = Array.isArray(candidate.traits)
597
- ? candidate.traits.map((trait) => cleanHostActorText(trait, "")).filter(Boolean).slice(0, 5)
598
- : [];
599
- return {
600
- id,
601
- name: cleanHostActorText(candidate.name, "Host Actor Persona"),
602
- intent: cleanHostActorText(candidate.intent, "Evaluate the app with a public-safe synthetic goal."),
603
- traits: traits.length > 0 ? traits : ["public_safe", "synthetic_user"]
604
- };
605
- }
606
- function normalizeHostActorScenario(value) {
607
- if (!value || typeof value !== "object")
608
- return null;
609
- const candidate = value;
610
- const steps = Array.isArray(candidate.steps)
611
- ? candidate.steps.map((step) => cleanHostActorText(step, "")).filter(Boolean).slice(0, 8)
612
- : [];
613
- if (steps.length === 0)
614
- return null;
615
- return {
616
- id: safeArtifactToken(cleanHostActorText(candidate.id, "host-actor-scenario")).slice(0, 80) || "host-actor-scenario",
617
- title: cleanHostActorText(candidate.title, "Host Actor Scenario"),
618
- goal: cleanHostActorText(candidate.goal, "Exercise the primary public-safe app workflow."),
619
- steps
620
- };
621
- }
622
322
  export function normalizeHostActorRecommendedProof(value) {
623
323
  const proof = cleanHostActorText(value, "");
624
324
  if (!/\bhumanish\s+run\b/.test(proof)) {
@@ -629,58 +329,6 @@ export function normalizeHostActorRecommendedProof(value) {
629
329
  }
630
330
  return proof;
631
331
  }
632
- function failedHostActorPlan(args) {
633
- return {
634
- schema: "humanish.oss-host-actor-plan.v1",
635
- generatedAt: new Date().toISOString(),
636
- personas: [],
637
- recommendedProof: "Host actor plan was not available.",
638
- repo: args.repo,
639
- scenarios: [],
640
- source: "local-codex-exec",
641
- status: args.status,
642
- summary: cleanHostActorText(args.summary, "Host Codex actor plan failed.")
643
- };
644
- }
645
- function parseJsonObject(raw) {
646
- for (const line of raw.split(/\r?\n/).map((value) => value.trim()).filter(Boolean).reverse()) {
647
- if (line.startsWith("{") && line.endsWith("}")) {
648
- try {
649
- const parsed = JSON.parse(line);
650
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
651
- return parsed;
652
- }
653
- catch { }
654
- }
655
- }
656
- const fence = /```(?:json)?\s*([\s\S]*?)```/i.exec(raw);
657
- if (fence?.[1]) {
658
- try {
659
- const parsed = JSON.parse(fence[1].trim());
660
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
661
- return parsed;
662
- }
663
- catch { }
664
- }
665
- try {
666
- const parsed = JSON.parse(raw);
667
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
668
- }
669
- catch {
670
- const lastClose = raw.lastIndexOf("}");
671
- if (lastClose === -1)
672
- return null;
673
- for (let start = raw.lastIndexOf("{", lastClose); start >= 0; start = raw.lastIndexOf("{", start - 1)) {
674
- try {
675
- const parsed = JSON.parse(raw.slice(start, lastClose + 1));
676
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
677
- return parsed;
678
- }
679
- catch { }
680
- }
681
- return null;
682
- }
683
- }
684
332
  function cleanHostActorText(value, fallback) {
685
333
  const text = String(typeof value === "string" ? value : fallback)
686
334
  .replace(/sk-[A-Za-z0-9_-]{20,}/g, "[redacted-openai-key]")
@@ -696,7 +344,6 @@ export async function runOssMetaLab(options) {
696
344
  const cwd = path.resolve(options.cwd);
697
345
  const dryRun = options.dryRun === true;
698
346
  const liveRequested = !dryRun;
699
- const codexAppServerMode = codexAppServerModeRequested(process.env, options.codexAppServer === true);
700
347
  const warnings = [];
701
348
  const repos = normalizeOssRepoSlugs(options.repos);
702
349
  const count = options.count ?? DEFAULT_OSS_REPOS.length;
@@ -737,21 +384,34 @@ export async function runOssMetaLab(options) {
737
384
  };
738
385
  }
739
386
  const redactRepoNames = options.redactRepoNames ?? (liveRequested && (Boolean(githubTokenFromEnv(process.env)) || Boolean(options.repos?.length)));
740
- const hostActorMode = liveRequested && hostCodexActorRequested(process.env);
741
- const missingKeys = missingLiveKeys(process.env);
742
- if (liveRequested && missingKeys.length > 0) {
743
- warnings.push(`Live E2B/Codex launch is waiting on env vars: ${missingKeys.join(", ")}.`);
744
- warnings.push("Observer lanes stay in the live waiting state until keys are present.");
745
- }
746
- if (liveRequested && !githubTokenFromEnv(process.env)) {
747
- warnings.push("No GH_TOKEN, GITHUB_TOKEN, or GITHUB_PAT is present; public repos can clone, but private GitHub repos will fail access preflight.");
748
- }
749
387
  const assignments = buildOssRepoAssignments(repos, count);
750
388
  const publicAssignments = redactAssignments(assignments, redactRepoNames);
751
389
  const publicRepos = redactRepoNames ? publicAssignments.map((assignment) => assignment.repo) : repos;
390
+ if (liveRequested) {
391
+ return {
392
+ schema: OSS_META_LAB_SCHEMA,
393
+ ok: false,
394
+ assignments: publicAssignments,
395
+ count,
396
+ cwd,
397
+ dryRun,
398
+ error: {
399
+ code: "HUMANISH_OSS_META_LIVE_ISOLATION_REQUIRED",
400
+ message: "Live OSS meta-lab execution is unavailable because repository-derived instructions require an isolated credential boundary. Use --dry-run."
401
+ },
402
+ liveRequested,
403
+ repos: publicRepos,
404
+ sandboxes: [],
405
+ warnings
406
+ };
407
+ }
408
+ const codexAppServerMode = codexAppServerModeRequested(process.env, options.codexAppServer === true);
409
+ const hostActorMode = liveRequested && hostCodexActorRequested(process.env);
410
+ const missingKeys = missingLiveKeys(process.env);
752
411
  const runId = options.runId ?? makeMetaRunId();
412
+ const physicalCwd = await realpath(cwd);
753
413
  const runResult = await runDryRun({
754
- cwd,
414
+ cwd: physicalCwd,
755
415
  dryRun: true,
756
416
  runId,
757
417
  simCount: count
@@ -774,18 +434,18 @@ export async function runOssMetaLab(options) {
774
434
  warnings: [...warnings, ...runResult.warnings]
775
435
  };
776
436
  }
777
- const artifactRoot = path.join(cwd, ".humanish", "runs", runId);
437
+ const preparedRunPaths = await bindExistingRunArtifactPaths(physicalCwd, runId);
438
+ const artifactRoot = preparedRunPaths;
778
439
  const createdAt = new Date().toISOString();
779
440
  const source = await buildRunSource({
780
441
  capturedAt: createdAt,
781
- cwd,
442
+ cwd: physicalCwd,
782
443
  humanishSource: "present",
783
444
  packageName: "humanish"
784
445
  });
785
446
  const persistScreenshots = liveRequested;
786
447
  let liveDesktops = [];
787
448
  let substrateMissingKeys = [...missingKeys];
788
- await mkdir(artifactRoot, { recursive: true });
789
449
  const initialBundle = buildMetaBundle({
790
450
  assignments,
791
451
  createdAt,
@@ -799,9 +459,10 @@ export async function runOssMetaLab(options) {
799
459
  source
800
460
  });
801
461
  await writeMetaBundleArtifacts(artifactRoot, initialBundle);
802
- let observer = await renderObserver(cwd, runId, { open: false });
462
+ let observer = await renderObserver(physicalCwd, runId, { open: false });
803
463
  if (observer.ok && options.onObserverReady) {
804
464
  await options.onObserverReady(observer);
465
+ await validatePreparedRunArtifactPaths(preparedRunPaths);
805
466
  }
806
467
  const shouldPreflightRepoAccess = liveRequested
807
468
  && missingKeys.length === 0
@@ -968,7 +629,7 @@ export async function runOssMetaLab(options) {
968
629
  source
969
630
  });
970
631
  await writeMetaBundleArtifacts(artifactRoot, bundle);
971
- const finalObserver = await renderObserver(cwd, runId, { open: options.open === true });
632
+ const finalObserver = await renderObserver(physicalCwd, runId, { open: options.open === true });
972
633
  Object.assign(observer, finalObserver);
973
634
  if (observer.ok) {
974
635
  attachObserverRuntimeStreamUrls(observer, liveDesktops
@@ -1017,6 +678,7 @@ export async function runOssMetaLab(options) {
1017
678
  liveRequested,
1018
679
  missingKeys: substrateMissingKeys,
1019
680
  persistScreenshots,
681
+ physicalCwd,
1020
682
  redactRepoNames,
1021
683
  runId,
1022
684
  source,
@@ -1027,10 +689,10 @@ export async function runOssMetaLab(options) {
1027
689
  }
1028
690
  async function writeMetaBundleArtifacts(artifactRoot, bundle) {
1029
691
  const publicBundle = publicSafeOssMetaBundle(bundle);
1030
- await writeJson(path.join(artifactRoot, "run.json"), publicBundle);
1031
- await writeJson(path.join(artifactRoot, "review.json"), publicBundle.review);
1032
- await writeFile(path.join(artifactRoot, "review.md"), renderMetaReviewMarkdown(publicBundle), "utf8");
1033
- await writeFile(path.join(artifactRoot, "events.ndjson"), `${publicBundle.events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
692
+ await writeJson(artifactRoot, "run.json", publicBundle);
693
+ await writeJson(artifactRoot, "review.json", publicBundle.review);
694
+ await writeContainedOutputFile(artifactRoot, "review.md", renderMetaReviewMarkdown(publicBundle), "utf8");
695
+ await writeContainedOutputFile(artifactRoot, "events.ndjson", `${publicBundle.events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
1034
696
  }
1035
697
  export function publicSafeOssMetaBundle(bundle) {
1036
698
  return {
@@ -1150,27 +812,12 @@ export function startOssMetaLabLiveRefresh(result, options = {}) {
1150
812
  };
1151
813
  }
1152
814
  function cleanupOssMetaLabLiveDesktops(liveDesktops, options = {}) {
1153
- const result = {
1154
- sandboxes: liveDesktops.map((desktop) => ({
1155
- repo: desktop.repo,
1156
- ...(desktop.sandboxId ? { sandboxId: desktop.sandboxId } : {}),
1157
- streamId: desktop.streamId,
1158
- urlPresent: Boolean(desktop.url)
1159
- }))
1160
- };
1161
- // BY-ID ONLY by default -- never Sandbox.list. This run's own sandboxIds (including ones whose
1162
- // bootstrap failed after Sandbox.create succeeded; see launchLiveDesktops above) are already
1163
- // tracked in `liveDesktops`, so no account-wide discovery is needed to reclaim what THIS run
1164
- // created. Explicit `includeProviderReadback: true` additionally sweeps stale provider-tagged
1165
- // sandboxes left by a crashed prior process; only the maintainer-only `humanish lab cleanup oss`
1166
- // command (cleanupStaleOssMetaLabSandboxes) needs that, and its own Sandbox.list call is
1167
- // further gated behind HUMANISH_OSS_META_ALLOW_PROVIDER_LIST (see listOssMetaLabProviderSandboxIds).
1168
- return options.includeProviderReadback === true
1169
- ? cleanupOssMetaLabSandboxesAndProviderMatches(result, options)
1170
- : cleanupOssMetaLabSandboxes(result, options);
1171
- }
1172
- export function sandboxIdsForOssMetaLabCleanup(result) {
1173
- return [...new Set(result.sandboxes.flatMap((sandbox) => sandbox.sandboxId ? [sandbox.sandboxId] : []))];
815
+ const ids = [...new Set(liveDesktops.flatMap((entry) => entry.desktop?.sandboxId ? [entry.desktop.sandboxId] : []))];
816
+ return killOssMetaLabProviderSandboxIds(ids, {
817
+ ...(options.killSandbox === undefined ? {} : { killSandbox: options.killSandbox }),
818
+ ...(options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.requestTimeoutMs }),
819
+ skipped: liveDesktops.length - ids.length
820
+ });
1174
821
  }
1175
822
  export async function cleanupStaleOssMetaLabSandboxes(options = {}) {
1176
823
  return cleanupOssMetaLabSandboxesAndProviderMatches({ sandboxes: [] }, options);
@@ -1181,18 +828,11 @@ export async function cleanupOssMetaLabSandboxesAndProviderMatches(result, optio
1181
828
  ...(options.listSandboxes === undefined ? {} : { listSandboxes: options.listSandboxes }),
1182
829
  requestTimeoutMs
1183
830
  });
1184
- const ids = [...new Set([...sandboxIdsForOssMetaLabCleanup(result), ...listed.ids])];
1185
- const cleanup = await cleanupOssMetaLabSandboxes({
1186
- sandboxes: ids.map((sandboxId, index) => ({
1187
- repo: "oss-meta-lab",
1188
- sandboxId,
1189
- streamId: `provider-${String(index + 1).padStart(2, "0")}`,
1190
- urlPresent: false
1191
- }))
1192
- }, {
831
+ const cleanup = await killOssMetaLabProviderSandboxIds(listed.ids, {
1193
832
  ...(options.killSandbox === undefined ? {} : { killSandbox: options.killSandbox }),
1194
833
  redactIds: true,
1195
- requestTimeoutMs
834
+ requestTimeoutMs,
835
+ skipped: listed.skipped
1196
836
  });
1197
837
  const remaining = listed.errors.length > 0 || cleanup.errors.length > 0
1198
838
  ? undefined
@@ -1202,19 +842,24 @@ export async function cleanupOssMetaLabSandboxesAndProviderMatches(result, optio
1202
842
  })).ids.length;
1203
843
  return {
1204
844
  killed: cleanup.killed,
1205
- matched: ids.length,
845
+ matched: listed.ids.length,
1206
846
  ...(remaining === undefined ? {} : { remaining }),
1207
- skipped: result.sandboxes.length - sandboxIdsForOssMetaLabCleanup(result).length + listed.skipped + cleanup.skipped,
1208
- errors: [...listed.errors, ...cleanup.errors]
847
+ skipped: result.sandboxes.length + cleanup.skipped,
848
+ errors: [
849
+ ...(result.sandboxes.length > 0
850
+ ? ["Stored OSS meta-lab sandbox IDs were not used; cleanup requires verified provider metadata."]
851
+ : []),
852
+ ...listed.errors,
853
+ ...cleanup.errors
854
+ ]
1209
855
  };
1210
856
  }
1211
857
  async function listOssMetaLabProviderSandboxIds(options) {
1212
858
  let listSandboxes = options.listSandboxes;
1213
859
  if (!listSandboxes) {
1214
860
  // humanish never enumerates an operator's E2B account by default (see
1215
- // docs/principles/invariants-and-defaults.md): reclaiming THIS run's own sandboxes stays
1216
- // by-id-only (sandboxIdsForOssMetaLabCleanup / cleanupOssMetaLabSandboxes). Real, account-wide
1217
- // Sandbox.list discovery exists ONLY for a maintainer's explicit orphan sweep of a crashed
861
+ // docs/principles/invariants-and-defaults.md). Account-wide Sandbox.list discovery exists
862
+ // ONLY for a maintainer's explicit orphan sweep of a crashed
1218
863
  // prior process (`humanish lab cleanup oss`) and requires this opt-in, set deliberately by the
1219
864
  // maintainer running it against their own account -- never a default, never for a shared key.
1220
865
  if (process.env.HUMANISH_OSS_META_ALLOW_PROVIDER_LIST !== "1") {
@@ -1297,9 +942,18 @@ function isCleanupEligibleOssMetaLabSandbox(sandbox) {
1297
942
  const state = typeof sandbox.state === "string" ? sandbox.state.toLowerCase() : "";
1298
943
  return !["closed", "killed", "paused", "terminated"].includes(state);
1299
944
  }
1300
- export async function cleanupOssMetaLabSandboxes(result, options = {}) {
1301
- const ids = sandboxIdsForOssMetaLabCleanup(result);
1302
- const skipped = result.sandboxes.length - ids.length;
945
+ export async function cleanupOssMetaLabSandboxes(result, _options = {}) {
946
+ return {
947
+ killed: 0,
948
+ skipped: result.sandboxes.length,
949
+ errors: result.sandboxes.some((sandbox) => sandbox.sandboxId)
950
+ ? ["Stored OSS meta-lab sandbox IDs cannot authorize provider mutation; use the explicit metadata-verified orphan sweep."]
951
+ : []
952
+ };
953
+ }
954
+ async function killOssMetaLabProviderSandboxIds(idsInput, options = {}) {
955
+ const ids = [...new Set(idsInput.filter((id) => id.trim()))];
956
+ const skipped = options.skipped ?? 0;
1303
957
  if (ids.length === 0) {
1304
958
  return { killed: 0, skipped, errors: [] };
1305
959
  }
@@ -2270,11 +1924,8 @@ async function launchLiveDesktops(assignments, options = {}) {
2270
1924
  return Promise.all(assignments.map(async (assignment) => {
2271
1925
  const repoLabel = options.redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo;
2272
1926
  const hostActorPlanResult = options.hostActorPlansByStream?.get(assignment.streamId);
2273
- // Hoisted OUTSIDE the try so a failure after Sandbox.create succeeds (bootstrap/stream) still
2274
- // lets the catch branch report the sandboxId it already created. Without this, a live sandbox
2275
- // that failed to bootstrap had no id anywhere in this run's own result, and the ONLY way to
2276
- // reclaim it was Sandbox.list account-wide discovery. Capturing it here is what lets cleanup
2277
- // stay by-id-only for this run (see cleanupOssMetaLabLiveDesktops below).
1927
+ // Retain the provider object in process memory so attached cleanup can use a trusted handle.
1928
+ // Durable result IDs are evidence only and never authorize provider mutation.
2278
1929
  let desktop;
2279
1930
  try {
2280
1931
  desktop = await desktopModule.Sandbox.create({
@@ -2288,8 +1939,7 @@ async function launchLiveDesktops(assignments, options = {}) {
2288
1939
  },
2289
1940
  envs: {
2290
1941
  ...collectOssMetaLabRemoteEnv(process.env),
2291
- ...(options.codexAppServerMode ? { HUMANISH_OSS_META_CODEX_APP_SERVER: "1" } : {}),
2292
- ...collectOssMetaLabPrivateEnv(process.env)
1942
+ ...(options.codexAppServerMode ? { HUMANISH_OSS_META_CODEX_APP_SERVER: "1" } : {})
2293
1943
  },
2294
1944
  resolution: [1440, 960],
2295
1945
  dpi: 96,
@@ -2327,12 +1977,12 @@ async function launchLiveDesktops(assignments, options = {}) {
2327
1977
  catch (error) {
2328
1978
  return {
2329
1979
  error: compactError(error),
1980
+ ...(desktop ? { desktop } : {}),
2330
1981
  ...(hostActorPlanResult?.plan ? { hostActorPlan: hostActorPlanResult.plan } : {}),
2331
1982
  ...(hostActorPlanResult?.artifactPath ? { hostActorPlanPath: hostActorPlanResult.artifactPath } : {}),
2332
1983
  repo: repoLabel,
2333
- // The sandbox may have been created before the failure (e.g. bootstrap threw): if so,
2334
- // its id rides the result too, so THIS run's own cleanup can reclaim it by id without
2335
- // ever listing the account.
1984
+ // Keep the id as public-safe lifecycle evidence only. Cleanup authority comes from the
1985
+ // in-memory provider object above or a separately verified provider-metadata sweep.
2336
1986
  ...(desktop?.sandboxId ? { sandboxId: desktop.sandboxId } : {}),
2337
1987
  simId: assignment.simId,
2338
1988
  streamId: assignment.streamId
@@ -2410,7 +2060,7 @@ async function refreshOssMetaLabLiveRuntime(runtime, options) {
2410
2060
  source: runtime.source
2411
2061
  });
2412
2062
  await writeMetaBundleArtifacts(runtime.artifactRoot, bundle);
2413
- await renderObserver(runtime.cwd, runtime.runId, { open: false });
2063
+ await renderObserver(runtime.physicalCwd, runtime.runId, { open: false });
2414
2064
  }
2415
2065
  async function refreshLiveDesktopProgress(liveDesktops, options) {
2416
2066
  const requestTimeoutMs = readPositiveInt(process.env.HUMANISH_E2B_REQUEST_TIMEOUT_MS, 60_000);
@@ -2469,8 +2119,6 @@ async function captureLiveDesktopScreenshots(artifactRoot, liveDesktops, options
2469
2119
  if (candidates.length === 0) {
2470
2120
  return { warnings: [] };
2471
2121
  }
2472
- const screenshotRoot = path.join(artifactRoot, "screenshots");
2473
- await mkdir(screenshotRoot, { recursive: true });
2474
2122
  const warnings = [];
2475
2123
  await Promise.all(candidates.map(async (desktop) => {
2476
2124
  if (!desktop.desktop) {
@@ -2481,8 +2129,7 @@ async function captureLiveDesktopScreenshots(artifactRoot, liveDesktops, options
2481
2129
  await desktop.desktop.wait(readPositiveInt(process.env.HUMANISH_OSS_META_SCREENSHOT_SETTLE_MS, 2_500)).catch(() => undefined);
2482
2130
  const bytes = await desktop.desktop.screenshot("bytes");
2483
2131
  const fileName = `${safeArtifactToken(desktop.streamId)}.png`;
2484
- const screenshotPath = path.join(screenshotRoot, fileName);
2485
- await writeFile(screenshotPath, Buffer.from(bytes));
2132
+ await writeContainedOutputFile(artifactRoot, path.join("screenshots", fileName), Buffer.from(bytes));
2486
2133
  desktop.screenshot = {
2487
2134
  capturedAt: new Date().toISOString(),
2488
2135
  observerUrl: `../screenshots/${fileName}`,
@@ -2513,12 +2160,6 @@ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options)
2513
2160
  if (candidates.length === 0) {
2514
2161
  return { warnings: [] };
2515
2162
  }
2516
- const actorEvidenceRoot = path.join(artifactRoot, "actor-evidence");
2517
- const nestedEvidenceRoot = path.join(artifactRoot, "nested-evidence");
2518
- const setupQualityRoot = path.join(artifactRoot, "setup-quality");
2519
- await mkdir(actorEvidenceRoot, { recursive: true });
2520
- await mkdir(nestedEvidenceRoot, { recursive: true });
2521
- await mkdir(setupQualityRoot, { recursive: true });
2522
2163
  let written = 0;
2523
2164
  for (const desktop of candidates) {
2524
2165
  const assignment = options.assignments.find((candidate) => candidate.streamId === desktop.streamId);
@@ -2527,7 +2168,7 @@ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options)
2527
2168
  const actorEvidence = {};
2528
2169
  if (desktop.completion?.actorLastMessageTail) {
2529
2170
  const relativePath = path.join("actor-evidence", `${baseName}-actor-last-message-tail.txt`);
2530
- await writeFile(path.join(artifactRoot, relativePath), renderPublicSafeActorEvidenceText("actor-last-message", desktop.streamId, desktop.completion.actorLastMessageTail, {
2171
+ await writeContainedOutputFile(artifactRoot, relativePath, renderPublicSafeActorEvidenceText("actor-last-message", desktop.streamId, desktop.completion.actorLastMessageTail, {
2531
2172
  providerRuntimeId: options.redactRepoNames ? desktop.sandboxId : undefined,
2532
2173
  repo: repoForRedaction
2533
2174
  }), "utf8");
@@ -2536,7 +2177,7 @@ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options)
2536
2177
  }
2537
2178
  if (desktop.completion?.actorLogTail) {
2538
2179
  const relativePath = path.join("actor-evidence", `${baseName}-actor-log-tail.txt`);
2539
- await writeFile(path.join(artifactRoot, relativePath), renderPublicSafeActorEvidenceText("actor-log", desktop.streamId, desktop.completion.actorLogTail, {
2180
+ await writeContainedOutputFile(artifactRoot, relativePath, renderPublicSafeActorEvidenceText("actor-log", desktop.streamId, desktop.completion.actorLogTail, {
2540
2181
  providerRuntimeId: options.redactRepoNames ? desktop.sandboxId : undefined,
2541
2182
  repo: repoForRedaction
2542
2183
  }), "utf8");
@@ -2548,7 +2189,7 @@ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options)
2548
2189
  const snapshot = options.redactRepoNames
2549
2190
  ? redactSetupQualityRepoMentions(suppressSetupQualityPreviews(desktop.completion.setupQuality), repoForRedaction)
2550
2191
  : desktop.completion.setupQuality;
2551
- await writeJson(path.join(artifactRoot, relativePath), snapshot);
2192
+ await writeJson(artifactRoot, relativePath, snapshot);
2552
2193
  actorEvidence.setupQualityPath = relativePath;
2553
2194
  written += 1;
2554
2195
  }
@@ -2556,7 +2197,7 @@ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options)
2556
2197
  || desktop.completion.nestedStepTraceSummary !== undefined
2557
2198
  || desktop.completion.nestedVerifyPassed !== undefined)) {
2558
2199
  const relativePath = path.join("nested-evidence", `${baseName}-nested-proof.json`);
2559
- await writeJson(path.join(artifactRoot, relativePath), {
2200
+ await writeJson(artifactRoot, relativePath, {
2560
2201
  schema: "humanish.oss-meta-nested-proof.v1",
2561
2202
  streamId: desktop.streamId,
2562
2203
  redaction: {
@@ -2580,8 +2221,7 @@ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options)
2580
2221
  const tracePath = path.join("codex-app-server", `${baseName}-summary.json`);
2581
2222
  const eventsPath = path.join("codex-app-server", `${baseName}-events.ndjson`);
2582
2223
  const transcriptPath = path.join("codex-app-server", `${baseName}-transcript.txt`);
2583
- await mkdir(path.join(artifactRoot, "codex-app-server"), { recursive: true });
2584
- await writeJson(path.join(artifactRoot, tracePath), isRecord(evidence.traceJson)
2224
+ await writeJson(artifactRoot, tracePath, isRecord(evidence.traceJson)
2585
2225
  ? evidence.traceJson
2586
2226
  : {
2587
2227
  schema: "humanish.codex-app-server-trace.missing.v1",
@@ -2590,8 +2230,8 @@ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options)
2590
2230
  reason: evidence.reason ?? "Remote app-server trace JSON was not captured.",
2591
2231
  traceText: evidence.traceText ?? ""
2592
2232
  });
2593
- await writeFile(path.join(artifactRoot, eventsPath), evidence.eventsText ?? "No app-server event envelope tail captured.\n", "utf8");
2594
- await writeFile(path.join(artifactRoot, transcriptPath), evidence.transcriptText ?? "No app-server transcript tail captured.\n", "utf8");
2233
+ await writeContainedOutputFile(artifactRoot, eventsPath, evidence.eventsText ?? "No app-server event envelope tail captured.\n", "utf8");
2234
+ await writeContainedOutputFile(artifactRoot, transcriptPath, evidence.transcriptText ?? "No app-server transcript tail captured.\n", "utf8");
2595
2235
  evidence.tracePath = tracePath;
2596
2236
  evidence.eventsPath = eventsPath;
2597
2237
  evidence.transcriptPath = transcriptPath;
@@ -4769,8 +4409,8 @@ async function runDesktopCommand(desktop, command, options) {
4769
4409
  }
4770
4410
  async function packLocalHumanishPackage(cwd, runId) {
4771
4411
  const packageRoot = moduleRoot;
4772
- const packDir = path.join(cwd, ".humanish", "tmp", "oss-meta", runId, "package");
4773
- await mkdir(packDir, { recursive: true });
4412
+ await prepareReusableHumanishStorageDirectory(cwd, "tmp", "oss-meta", runId);
4413
+ const packDir = await prepareReusableHumanishStorageDirectory(cwd, "tmp", "oss-meta", runId, "package");
4774
4414
  await execFileAsync("pnpm", ["build"], {
4775
4415
  cwd: packageRoot,
4776
4416
  env: process.env,
@@ -4881,8 +4521,8 @@ function safeArtifactToken(value) {
4881
4521
  const token = value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
4882
4522
  return token || "artifact";
4883
4523
  }
4884
- async function writeJson(filePath, value) {
4885
- await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
4524
+ async function writeJson(artifactRoot, relativePath, value) {
4525
+ await writeContainedOutputFile(artifactRoot, relativePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
4886
4526
  }
4887
4527
  // E2B desktop interfaces + the optional-peer loader now live in ./e2b-desktop-launch.js
4888
4528
  // (shared with the computer-use actor lane) and are imported at the top of this file.