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.
- package/README.md +33 -31
- package/dist/claude-agent-sdk.js +14 -7
- package/dist/claude-agent-sdk.js.map +1 -1
- package/dist/codex-app-server-ui.d.ts +1 -1
- package/dist/codex-app-server-ui.js +69 -39
- package/dist/codex-app-server-ui.js.map +1 -1
- package/dist/codex-app-server.d.ts +3 -0
- package/dist/codex-app-server.js +15 -8
- package/dist/codex-app-server.js.map +1 -1
- package/dist/concurrent-shared-world-lab.js +18 -16
- package/dist/concurrent-shared-world-lab.js.map +1 -1
- package/dist/core/git-state.d.ts +2 -0
- package/dist/core/git-state.js +152 -17
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/git-workspace.d.ts +31 -0
- package/dist/core/git-workspace.js +447 -0
- package/dist/core/git-workspace.js.map +1 -0
- package/dist/cua-actor-lab.d.ts +3 -2
- package/dist/cua-actor-lab.js +34 -23
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/e2b-terminal-lab.js +111 -46
- package/dist/e2b-terminal-lab.js.map +1 -1
- package/dist/feedback.js +139 -101
- package/dist/feedback.js.map +1 -1
- package/dist/init.d.ts +1 -1
- package/dist/init.js +108 -22
- package/dist/init.js.map +1 -1
- package/dist/lab-config.d.ts +5 -0
- package/dist/lab-config.js +38 -0
- package/dist/lab-config.js.map +1 -1
- package/dist/labs.js +258 -52
- package/dist/labs.js.map +1 -1
- package/dist/observer-assets.js +3 -2
- package/dist/observer-assets.js.map +1 -1
- package/dist/observer-static.js +131 -17
- package/dist/observer-static.js.map +1 -1
- package/dist/observer.js +263 -66
- package/dist/observer.js.map +1 -1
- package/dist/openai-responses-cu.js +18 -4
- package/dist/openai-responses-cu.js.map +1 -1
- package/dist/oss-lab.js +59 -14
- package/dist/oss-lab.js.map +1 -1
- package/dist/oss-meta-lab.d.ts +2 -4
- package/dist/oss-meta-lab.js +117 -477
- package/dist/oss-meta-lab.js.map +1 -1
- package/dist/program.js +23 -21
- package/dist/program.js.map +1 -1
- package/dist/run-paths.d.ts +56 -0
- package/dist/run-paths.js +340 -0
- package/dist/run-paths.js.map +1 -0
- package/dist/run.d.ts +14 -4
- package/dist/run.js +595 -306
- package/dist/run.js.map +1 -1
- package/dist/scripted-browser-actor.d.ts +7 -4
- package/dist/scripted-browser-actor.js +154 -41
- package/dist/scripted-browser-actor.js.map +1 -1
- package/dist/scripted-browser-lab.js +95 -45
- package/dist/scripted-browser-lab.js.map +1 -1
- package/dist/selected-output-paths.d.ts +49 -0
- package/dist/selected-output-paths.js +354 -0
- package/dist/selected-output-paths.js.map +1 -0
- package/dist/shared-world-lab.js +13 -12
- package/dist/shared-world-lab.js.map +1 -1
- package/dist/source-archive.js +44 -1
- package/dist/source-archive.js.map +1 -1
- package/docs/architecture/oss-lab-poc.md +8 -0
- package/docs/assets/humanish-observer-hero.png +0 -0
- package/docs/contracts/policy.md +7 -1
- package/docs/goals/current.md +20 -1
- package/docs/product/open-source-install-experience.md +9 -1
- package/docs/ramp/README.md +14 -7
- package/package.json +1 -1
package/dist/oss-meta-lab.js
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
270
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
1031
|
-
await writeJson(
|
|
1032
|
-
await
|
|
1033
|
-
await
|
|
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
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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
|
|
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
|
|
1208
|
-
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)
|
|
1216
|
-
//
|
|
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,
|
|
1301
|
-
|
|
1302
|
-
|
|
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
|
-
//
|
|
2274
|
-
//
|
|
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
|
-
//
|
|
2334
|
-
//
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
2594
|
-
await
|
|
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
|
-
|
|
4773
|
-
await
|
|
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(
|
|
4885
|
-
await
|
|
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.
|