mimetic-cli 0.1.3 → 0.1.5

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 (50) hide show
  1. package/README.md +67 -12
  2. package/dist/env-file.d.ts +14 -0
  3. package/dist/env-file.js +108 -0
  4. package/dist/env-file.js.map +1 -0
  5. package/dist/feedback.d.ts +7 -5
  6. package/dist/feedback.js +61 -4
  7. package/dist/feedback.js.map +1 -1
  8. package/dist/init-templates.js +29 -0
  9. package/dist/init-templates.js.map +1 -1
  10. package/dist/lab-app-runner.d.ts +78 -0
  11. package/dist/lab-app-runner.js +403 -0
  12. package/dist/lab-app-runner.js.map +1 -0
  13. package/dist/labs.d.ts +67 -0
  14. package/dist/labs.js +257 -0
  15. package/dist/labs.js.map +1 -0
  16. package/dist/observer-assets.js +473 -25
  17. package/dist/observer-assets.js.map +1 -1
  18. package/dist/observer.d.ts +6 -0
  19. package/dist/observer.js +49 -8
  20. package/dist/observer.js.map +1 -1
  21. package/dist/oss-lab.d.ts +1 -1
  22. package/dist/oss-lab.js +6 -6
  23. package/dist/oss-lab.js.map +1 -1
  24. package/dist/oss-meta-lab.d.ts +113 -1
  25. package/dist/oss-meta-lab.js +2753 -200
  26. package/dist/oss-meta-lab.js.map +1 -1
  27. package/dist/oss-remote-telemetry.d.ts +77 -0
  28. package/dist/oss-remote-telemetry.js +393 -0
  29. package/dist/oss-remote-telemetry.js.map +1 -0
  30. package/dist/program.d.ts +8 -0
  31. package/dist/program.js +668 -70
  32. package/dist/program.js.map +1 -1
  33. package/dist/run.d.ts +105 -3
  34. package/dist/run.js +684 -22
  35. package/dist/run.js.map +1 -1
  36. package/docs/architecture/local-codex-tui-actor.md +9 -6
  37. package/docs/architecture/oss-lab-poc.md +119 -47
  38. package/docs/architecture/project-layout.md +40 -6
  39. package/docs/assets/mimetic-oss-lab-observer.png +0 -0
  40. package/docs/contracts/feedback.md +15 -12
  41. package/docs/contracts/policy.md +9 -2
  42. package/docs/contracts/run-bundle.md +62 -0
  43. package/docs/contracts/schemas.md +21 -0
  44. package/docs/goals/current.md +50 -17
  45. package/docs/product/open-source-install-experience.md +63 -8
  46. package/docs/ramp/README.md +26 -8
  47. package/docs/roadmap/world-class-open-source-v0.md +41 -20
  48. package/package.json +9 -6
  49. package/skills/mimetic-cli/SKILL.md +89 -4
  50. package/skills/mimetic-cli/agents/openai.yaml +1 -1
@@ -1,15 +1,31 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { randomBytes } from "node:crypto";
3
- import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
+ import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  import { fileURLToPath } from "node:url";
7
- import { renderObserver } from "./observer.js";
7
+ import { attachObserverRuntimeStreamUrls, renderObserver } from "./observer.js";
8
8
  import { DEFAULT_OSS_REPOS, normalizeOssRepoSlugs, validateOssRepoSlug } from "./oss-lab.js";
9
+ import { redactOssRemoteTelemetryText } from "./oss-remote-telemetry.js";
9
10
  import { REVIEW_SCHEMA, RUN_BUNDLE_SCHEMA, runDryRun } from "./run.js";
10
11
  export const OSS_META_LAB_SCHEMA = "mimetic.oss-meta-lab-result.v1";
11
12
  const execFileAsync = promisify(execFile);
12
13
  const moduleRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
+ const liveRuntimeByResult = new WeakMap();
15
+ const OSS_META_LAB_PROVIDER_METADATA = {
16
+ mode: "oss-meta-lab",
17
+ tool: "mimetic-cli"
18
+ };
19
+ const OSS_META_LAB_REMOTE_ENV_NAMES = [
20
+ "MIMETIC_OSS_META_ACTOR_FIRST",
21
+ "MIMETIC_OSS_META_ACTOR_MODEL",
22
+ "MIMETIC_OSS_META_HOST_CODEX_ACTOR",
23
+ "MIMETIC_OSS_META_ACTOR_TIMEOUT_MS",
24
+ "MIMETIC_OSS_META_REQUIRE_ACTOR"
25
+ ];
26
+ const OSS_META_LAB_ACTOR_AUTH_PLACEHOLDER = "CODEX_API_KEY or CODEX_ACCESS_TOKEN";
27
+ const OSS_META_LAB_ACTOR_PREFLIGHT_PLACEHOLDER = "Codex actor API quota/auth preflight";
28
+ const OSS_META_LAB_HOST_ACTOR_PLACEHOLDER = "host Codex actor plan";
13
29
  export function buildOssRepoAssignments(repos, count) {
14
30
  return Array.from({ length: count }, (_, index) => {
15
31
  const repo = repos[index % repos.length];
@@ -25,6 +41,644 @@ export function buildOssRepoAssignments(repos, count) {
25
41
  };
26
42
  });
27
43
  }
44
+ export function collectOssMetaLabRemoteEnv(env) {
45
+ const result = {};
46
+ for (const name of OSS_META_LAB_REMOTE_ENV_NAMES) {
47
+ const value = env[name]?.trim();
48
+ if (value) {
49
+ result[name] = value;
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ export function collectOssMetaLabPrivateEnv(env) {
55
+ const result = {};
56
+ const codexApiKey = env.CODEX_API_KEY?.trim() || env.OPENAI_API_KEY?.trim();
57
+ const codexAccessToken = env.CODEX_ACCESS_TOKEN?.trim();
58
+ const githubToken = githubTokenFromEnv(env);
59
+ if (codexApiKey) {
60
+ result.MIMETIC_CODEX_API_KEY = codexApiKey;
61
+ }
62
+ if (codexAccessToken) {
63
+ result.MIMETIC_CODEX_ACCESS_TOKEN = codexAccessToken;
64
+ }
65
+ if (githubToken) {
66
+ result.MIMETIC_GITHUB_TOKEN = githubToken;
67
+ }
68
+ return result;
69
+ }
70
+ function githubTokenFromEnv(env) {
71
+ return env.GH_TOKEN?.trim() || env.GITHUB_TOKEN?.trim() || env.GITHUB_PAT?.trim() || "";
72
+ }
73
+ async function withGitHubAskPassEnv(root, env, callback) {
74
+ const gitEnv = await createGitHubAskPassEnv(root, env);
75
+ return callback(gitEnv);
76
+ }
77
+ async function createGitHubAskPassEnv(root, env) {
78
+ await mkdir(root, { recursive: true });
79
+ const askPassPath = path.join(root, `git-askpass-${randomBytes(4).toString("hex")}.sh`);
80
+ await writeFile(askPassPath, [
81
+ "#!/usr/bin/env bash",
82
+ "case \"$1\" in",
83
+ " *Username*) echo \"x-access-token\" ;;",
84
+ " *Password*) echo \"${MIMETIC_GITHUB_TOKEN_RUNTIME:-}\" ;;",
85
+ " *) echo \"\" ;;",
86
+ "esac",
87
+ ""
88
+ ].join("\n"), { encoding: "utf8", mode: 0o700 });
89
+ const token = githubTokenFromEnv(env);
90
+ return {
91
+ ...gitCredentialIsolatedEnv(env),
92
+ GIT_ASKPASS: askPassPath,
93
+ GIT_TERMINAL_PROMPT: "0",
94
+ ...(token ? { MIMETIC_GITHUB_TOKEN_RUNTIME: token } : {})
95
+ };
96
+ }
97
+ function gitEnvWithoutGitHubToken(env) {
98
+ return {
99
+ ...gitCredentialIsolatedEnv(env),
100
+ GIT_ASKPASS: "false",
101
+ SSH_ASKPASS: "false",
102
+ GIT_TERMINAL_PROMPT: "0"
103
+ };
104
+ }
105
+ function gitCredentialIsolatedEnv(env) {
106
+ const isolated = { ...env };
107
+ for (const key of Object.keys(isolated)) {
108
+ if (key.startsWith("GIT_CONFIG_")) {
109
+ delete isolated[key];
110
+ }
111
+ }
112
+ delete isolated.GH_TOKEN;
113
+ delete isolated.GITHUB_PAT;
114
+ delete isolated.GITHUB_TOKEN;
115
+ delete isolated.GIT_ASKPASS;
116
+ delete isolated.MIMETIC_GITHUB_TOKEN_RUNTIME;
117
+ delete isolated.SSH_ASKPASS;
118
+ return {
119
+ ...isolated,
120
+ GIT_CONFIG_GLOBAL: "/dev/null",
121
+ GIT_CONFIG_NOSYSTEM: "1",
122
+ GIT_TERMINAL_PROMPT: "0"
123
+ };
124
+ }
125
+ async function runGitRepoAccessProbe(execImpl, cwd, repoUrl, env, sourceEnv) {
126
+ await execImpl("git", ["-c", "credential.helper=", "ls-remote", "--exit-code", repoUrl, "HEAD"], {
127
+ cwd,
128
+ env,
129
+ maxBuffer: 256 * 1024,
130
+ timeout: readPositiveInt(sourceEnv.MIMETIC_OSS_META_REPO_PREFLIGHT_TIMEOUT_MS, 45_000)
131
+ });
132
+ }
133
+ export async function preflightOssMetaRepoAccess(args) {
134
+ const execImpl = args.execFileImpl ?? execFileAsync;
135
+ const tokenPresent = Boolean(githubTokenFromEnv(args.env));
136
+ const root = path.join(args.cwd, ".mimetic", "tmp", `repo-access-${randomBytes(4).toString("hex")}`);
137
+ try {
138
+ await mkdir(root, { recursive: true });
139
+ const tokenGitEnv = tokenPresent ? await createGitHubAskPassEnv(root, args.env) : undefined;
140
+ const anonymousGitEnv = gitEnvWithoutGitHubToken(args.env);
141
+ const results = [];
142
+ for (const assignment of args.assignments) {
143
+ const repoUrl = `https://github.com/${assignment.repo}.git`;
144
+ let anonymousError;
145
+ try {
146
+ await runGitRepoAccessProbe(execImpl, root, repoUrl, anonymousGitEnv, args.env);
147
+ results.push({
148
+ ok: true,
149
+ reason: tokenPresent
150
+ ? "GitHub repo clone access preflight passed with anonymous public clone access."
151
+ : "GitHub repo clone access preflight passed without token auth.",
152
+ repo: assignment.repo,
153
+ streamId: assignment.streamId,
154
+ tokenPresent
155
+ });
156
+ continue;
157
+ }
158
+ catch (error) {
159
+ anonymousError = error;
160
+ }
161
+ let tokenError;
162
+ if (tokenGitEnv) {
163
+ try {
164
+ await runGitRepoAccessProbe(execImpl, root, repoUrl, tokenGitEnv, args.env);
165
+ results.push({
166
+ ok: true,
167
+ reason: "GitHub repo clone access preflight passed with token auth after anonymous clone access failed.",
168
+ repo: assignment.repo,
169
+ streamId: assignment.streamId,
170
+ tokenPresent: true
171
+ });
172
+ continue;
173
+ }
174
+ catch (error) {
175
+ tokenError = error;
176
+ }
177
+ }
178
+ results.push({
179
+ ok: false,
180
+ reason: repoAccessFailureReason({
181
+ anonymousError,
182
+ redactRepoName: args.redactRepoNames === true,
183
+ repo: assignment.repo,
184
+ tokenError,
185
+ tokenPresent
186
+ }),
187
+ repo: assignment.repo,
188
+ streamId: assignment.streamId,
189
+ tokenPresent
190
+ });
191
+ }
192
+ return results;
193
+ }
194
+ finally {
195
+ await rm(root, { recursive: true, force: true }).catch(() => undefined);
196
+ }
197
+ }
198
+ export async function preflightOssMetaActorApiKey(args) {
199
+ const apiKey = args.env.CODEX_API_KEY?.trim() || args.env.OPENAI_API_KEY?.trim();
200
+ if (!apiKey) {
201
+ return {
202
+ ok: true,
203
+ reason: "No API-key actor auth present; preflight skipped."
204
+ };
205
+ }
206
+ const fetchImpl = args.fetchImpl ?? fetch;
207
+ const model = args.env.MIMETIC_OSS_META_ACTOR_PREFLIGHT_MODEL?.trim() || "gpt-4.1-mini";
208
+ try {
209
+ const response = await fetchImpl("https://api.openai.com/v1/responses", {
210
+ method: "POST",
211
+ headers: {
212
+ Authorization: `Bearer ${apiKey}`,
213
+ "Content-Type": "application/json"
214
+ },
215
+ body: JSON.stringify({
216
+ input: "Return a JSON object exactly like {\"status\":\"passed\"}. This is an actor auth preflight.",
217
+ max_output_tokens: 32,
218
+ model,
219
+ text: {
220
+ format: {
221
+ type: "json_object"
222
+ }
223
+ }
224
+ })
225
+ });
226
+ if (response.ok) {
227
+ return {
228
+ ok: true,
229
+ reason: `OpenAI actor API-key preflight passed for ${model}.`,
230
+ status: response.status
231
+ };
232
+ }
233
+ const body = await response.text().catch(() => "");
234
+ return {
235
+ ok: false,
236
+ reason: compactError(`OpenAI actor API-key preflight failed with HTTP ${response.status}: ${body}`),
237
+ status: response.status
238
+ };
239
+ }
240
+ catch (error) {
241
+ return {
242
+ ok: false,
243
+ reason: compactError(error)
244
+ };
245
+ }
246
+ }
247
+ function hostCodexActorRequested(env) {
248
+ return env.MIMETIC_OSS_META_HOST_CODEX_ACTOR === "1";
249
+ }
250
+ function remoteActorAuthRequested(env) {
251
+ return env.MIMETIC_OSS_META_ACTOR_FIRST === "1" || env.MIMETIC_OSS_META_REQUIRE_ACTOR === "1";
252
+ }
253
+ function actorRequired(env) {
254
+ return env.MIMETIC_OSS_META_REQUIRE_ACTOR === "1";
255
+ }
256
+ async function createHostActorPlans(args) {
257
+ return Promise.all(args.assignments.map((assignment) => createHostActorPlan({
258
+ assignment,
259
+ cwd: args.cwd,
260
+ redactRepoNames: args.redactRepoNames,
261
+ runId: args.runId
262
+ })));
263
+ }
264
+ async function createHostActorPlan(args) {
265
+ const token = repoSlugToken(args.assignment.repo);
266
+ const actorRoot = path.join(args.cwd, ".mimetic", "runs", args.runId, "host-actors", token);
267
+ const artifactPath = path.join("host-actors", token, "actor-plan.json");
268
+ const tmpRoot = path.join(args.cwd, ".mimetic", "tmp", "host-actors", args.runId, token);
269
+ const repoDir = path.join(tmpRoot, "repo");
270
+ const planPath = path.join(actorRoot, "actor-plan.json");
271
+ const schemaPath = path.join(tmpRoot, "actor-plan.schema.json");
272
+ await mkdir(actorRoot, { recursive: true });
273
+ if (args.redactRepoNames) {
274
+ const plan = failedHostActorPlan({
275
+ repo: "[redacted-authorized-repo]",
276
+ status: "blocked",
277
+ summary: "Host Codex actor plans are public-safe artifacts and require non-redacted public repo labels."
278
+ });
279
+ await writeJson(planPath, plan);
280
+ return {
281
+ artifactPath,
282
+ error: plan.summary,
283
+ plan,
284
+ planPath,
285
+ repo: args.assignment.repo,
286
+ streamId: args.assignment.streamId,
287
+ worktreePath: repoDir
288
+ };
289
+ }
290
+ try {
291
+ await mkdir(tmpRoot, { recursive: true });
292
+ await withGitHubAskPassEnv(tmpRoot, process.env, async (gitEnv) => {
293
+ await execFileAsync("git", ["clone", "--depth=1", `https://github.com/${args.assignment.repo}.git`, repoDir], {
294
+ cwd: tmpRoot,
295
+ env: gitEnv,
296
+ maxBuffer: 10 * 1024 * 1024,
297
+ timeout: readPositiveInt(process.env.MIMETIC_OSS_META_HOST_CLONE_TIMEOUT_MS, 90_000)
298
+ });
299
+ });
300
+ await writeJson(schemaPath, hostActorPlanJsonSchema());
301
+ const repoContext = await readHostActorRepoContext(repoDir);
302
+ const outputPath = path.join(tmpRoot, "codex-last-message.json");
303
+ const codexEnv = hostCodexEnv(process.env);
304
+ const codexCommand = [
305
+ "codex exec",
306
+ "--ephemeral",
307
+ "--ignore-user-config",
308
+ "--skip-git-repo-check",
309
+ "--dangerously-bypass-approvals-and-sandbox",
310
+ "-C",
311
+ shellQuote(repoDir),
312
+ "--output-schema",
313
+ shellQuote(schemaPath),
314
+ "--output-last-message",
315
+ shellQuote(outputPath),
316
+ shellQuote(buildHostActorPrompt(args.assignment.repo, repoContext)),
317
+ "< /dev/null"
318
+ ].join(" ");
319
+ const codexResult = await execFileAsync("bash", ["-lc", codexCommand], {
320
+ cwd: repoDir,
321
+ env: codexEnv,
322
+ maxBuffer: 10 * 1024 * 1024,
323
+ timeout: readPositiveInt(process.env.MIMETIC_OSS_META_HOST_ACTOR_TIMEOUT_MS, 240_000)
324
+ });
325
+ const rawPlan = await readFile(outputPath, "utf8").catch(() => {
326
+ const stdout = typeof codexResult.stdout === "string" ? codexResult.stdout : "";
327
+ const stderr = typeof codexResult.stderr === "string" ? codexResult.stderr : "";
328
+ return [stdout, stderr].filter((value) => value.trim()).join("\n");
329
+ });
330
+ await writeFile(path.join(actorRoot, "codex-output.txt"), `${sanitizeRemoteLog(rawPlan)}\n`, "utf8");
331
+ const plan = normalizeHostActorPlan(rawPlan, args.assignment.repo);
332
+ await writeJson(planPath, plan);
333
+ return {
334
+ artifactPath,
335
+ ...(plan.status === "passed" ? {} : { error: plan.summary }),
336
+ plan,
337
+ planPath,
338
+ repo: args.assignment.repo,
339
+ streamId: args.assignment.streamId,
340
+ worktreePath: repoDir
341
+ };
342
+ }
343
+ catch (error) {
344
+ const plan = failedHostActorPlan({
345
+ repo: args.assignment.repo,
346
+ status: "failed",
347
+ summary: compactError(error)
348
+ });
349
+ await writeJson(planPath, plan);
350
+ return {
351
+ artifactPath,
352
+ error: plan.summary,
353
+ plan,
354
+ planPath,
355
+ repo: args.assignment.repo,
356
+ streamId: args.assignment.streamId,
357
+ worktreePath: repoDir
358
+ };
359
+ }
360
+ finally {
361
+ await rm(tmpRoot, { recursive: true, force: true }).catch(() => undefined);
362
+ }
363
+ }
364
+ function hostCodexEnv(env) {
365
+ const allowed = [
366
+ "CODEX_HOME",
367
+ "HOME",
368
+ "LANG",
369
+ "LC_ALL",
370
+ "LC_CTYPE",
371
+ "LOGNAME",
372
+ "PATH",
373
+ "SHELL",
374
+ "TERM",
375
+ "TMP",
376
+ "TMPDIR",
377
+ "TEMP",
378
+ "USER",
379
+ "XDG_CACHE_HOME",
380
+ "XDG_CONFIG_HOME"
381
+ ];
382
+ return Object.fromEntries(allowed.flatMap((name) => {
383
+ const value = env[name];
384
+ return value === undefined ? [] : [[name, value]];
385
+ }));
386
+ }
387
+ async function readHostActorRepoContext(repoDir) {
388
+ const packageText = await readFile(path.join(repoDir, "package.json"), "utf8").catch(() => "");
389
+ const readmeText = await readFile(path.join(repoDir, "README.md"), "utf8").catch(() => "");
390
+ const indexText = await readFile(path.join(repoDir, "index.html"), "utf8").catch(() => "");
391
+ let packageSummary = "package.json missing";
392
+ try {
393
+ const pkg = JSON.parse(packageText);
394
+ packageSummary = JSON.stringify({
395
+ name: pkg.name,
396
+ packageManager: pkg.packageManager,
397
+ scripts: pkg.scripts ?? {},
398
+ dependencies: Object.keys(pkg.dependencies ?? {}).slice(0, 20),
399
+ devDependencies: Object.keys(pkg.devDependencies ?? {}).slice(0, 20)
400
+ }, null, 2);
401
+ }
402
+ catch { }
403
+ return [
404
+ "package_summary:",
405
+ packageSummary.slice(0, 2_500),
406
+ "",
407
+ "readme_excerpt:",
408
+ readmeText.replace(/\s+/g, " ").trim().slice(0, 2_000) || "(missing)",
409
+ "",
410
+ "index_excerpt:",
411
+ indexText.replace(/\s+/g, " ").trim().slice(0, 800) || "(missing)"
412
+ ].join("\n");
413
+ }
414
+ function repoAccessFailureReason(args) {
415
+ const anonymous = sanitizeRepoAccessError(args.anonymousError, args.repo, args.redactRepoName);
416
+ const token = args.tokenError
417
+ ? ` Token auth also failed: ${sanitizeRepoAccessError(args.tokenError, args.repo, args.redactRepoName)}`
418
+ : "";
419
+ const authHint = args.tokenPresent
420
+ ? "A GitHub token was present, but `git ls-remote` could not read the repo with token or anonymous access. Check token repo access and scopes."
421
+ : "No GitHub token was present. Public repos should pass unauthenticated; private repos need GH_TOKEN, GITHUB_TOKEN, or GITHUB_PAT with read access.";
422
+ return `${authHint} Anonymous access failed: ${anonymous}${token}`.replace(/\s+/g, " ").trim().slice(0, 420);
423
+ }
424
+ function sanitizeRepoAccessError(error, repo, redactRepoName) {
425
+ let message = compactError(error)
426
+ .replace(/github_pat_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
427
+ .replace(/\bMIMETIC_GITHUB_TOKEN_RUNTIME=[^\s]+/g, "MIMETIC_GITHUB_TOKEN_RUNTIME=[redacted-github-token]");
428
+ if (redactRepoName) {
429
+ message = message
430
+ .replaceAll(`https://github.com/${repo}.git`, "https://github.com/[redacted-authorized-repo].git")
431
+ .replaceAll(`github.com/${repo}.git`, "github.com/[redacted-authorized-repo].git")
432
+ .replaceAll(repo, "[redacted-authorized-repo]");
433
+ }
434
+ return message;
435
+ }
436
+ function blockedLiveDesktopsForRepoAccess(args) {
437
+ const checkedAt = new Date().toISOString();
438
+ const preflightByStream = new Map(args.preflight.map((result) => [result.streamId, result]));
439
+ const failedCount = args.preflight.filter((result) => !result.ok).length;
440
+ return args.assignments.map((assignment) => {
441
+ const repoLabel = args.redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo;
442
+ const preflight = preflightByStream.get(assignment.streamId);
443
+ const reason = preflight?.ok
444
+ ? `GitHub repo clone access preflight passed for ${repoLabel}, but live launch was skipped because ${failedCount} assigned repo${failedCount === 1 ? "" : "s"} failed preflight.`
445
+ : preflight?.reason ?? `GitHub repo clone access preflight did not run for ${repoLabel}.`;
446
+ return {
447
+ completion: {
448
+ actorStatus: "blocked",
449
+ appReason: reason,
450
+ appStatus: "blocked",
451
+ checkedAt,
452
+ logTail: reason,
453
+ nestedObserverPresent: false,
454
+ nestedVerifyPassed: false,
455
+ reason,
456
+ status: "blocked",
457
+ visualReason: "No headed desktop was launched before repo clone access was proven.",
458
+ visualStatus: "not_started",
459
+ visualWindowCount: 0
460
+ },
461
+ repo: repoLabel,
462
+ simId: assignment.simId,
463
+ streamId: assignment.streamId
464
+ };
465
+ });
466
+ }
467
+ function buildHostActorPrompt(repo, repoContext) {
468
+ return [
469
+ "You are a public-safe Mimetic host actor.",
470
+ "Use the bounded public repository context below to author a compact Mimetic setup plan.",
471
+ "Do not print secrets, environment values, private data, or long source snippets.",
472
+ "Do not commit, push, file issues, or mutate remotes.",
473
+ "Return only JSON matching the supplied schema.",
474
+ "",
475
+ `Repository: ${repo}`,
476
+ "",
477
+ "Plan requirements:",
478
+ "- status must be passed if you can infer useful public-safe personas/scenarios.",
479
+ "- Include exactly 1 or 2 synthetic personas.",
480
+ "- Include exactly 1 or 2 desktop/mobile browser scenarios.",
481
+ "- Scenario steps must be concise and public-safe.",
482
+ "- recommendedProof should name the strongest Mimetic command shape for this repo.",
483
+ "- Current Mimetic supports `mimetic run --app-url <loopback-url> --sims 2`; do not invent --browser, --viewport, --persona, or --scenario flags.",
484
+ "",
485
+ "Bounded public repo context:",
486
+ repoContext
487
+ ].join("\n");
488
+ }
489
+ function hostActorPlanJsonSchema() {
490
+ return {
491
+ type: "object",
492
+ additionalProperties: false,
493
+ required: ["status", "summary", "personas", "scenarios", "recommendedProof"],
494
+ properties: {
495
+ status: { type: "string", enum: ["passed", "blocked", "failed"] },
496
+ summary: { type: "string" },
497
+ personas: {
498
+ type: "array",
499
+ minItems: 1,
500
+ maxItems: 2,
501
+ items: {
502
+ type: "object",
503
+ additionalProperties: false,
504
+ required: ["id", "name", "intent", "traits"],
505
+ properties: {
506
+ id: { type: "string" },
507
+ name: { type: "string" },
508
+ intent: { type: "string" },
509
+ traits: {
510
+ type: "array",
511
+ minItems: 1,
512
+ maxItems: 5,
513
+ items: { type: "string" }
514
+ }
515
+ }
516
+ }
517
+ },
518
+ recommendedProof: { type: "string" },
519
+ scenarios: {
520
+ type: "array",
521
+ minItems: 1,
522
+ maxItems: 2,
523
+ items: {
524
+ type: "object",
525
+ additionalProperties: false,
526
+ required: ["id", "title", "goal", "steps"],
527
+ properties: {
528
+ id: { type: "string" },
529
+ title: { type: "string" },
530
+ goal: { type: "string" },
531
+ steps: {
532
+ type: "array",
533
+ minItems: 2,
534
+ maxItems: 8,
535
+ items: { type: "string" }
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+ };
542
+ }
543
+ function normalizeHostActorPlan(raw, repo) {
544
+ const parsed = parseJsonObject(raw);
545
+ if (!parsed) {
546
+ return failedHostActorPlan({
547
+ repo,
548
+ status: "failed",
549
+ summary: "Host Codex actor did not return parseable JSON."
550
+ });
551
+ }
552
+ const personas = Array.isArray(parsed.personas)
553
+ ? parsed.personas.map(normalizeHostActorPersona).filter((persona) => persona !== null).slice(0, 2)
554
+ : [];
555
+ const scenarios = Array.isArray(parsed.scenarios)
556
+ ? parsed.scenarios.map(normalizeHostActorScenario).filter((scenario) => scenario !== null).slice(0, 2)
557
+ : [];
558
+ const status = parsed.status === "blocked" || parsed.status === "failed" ? parsed.status : "passed";
559
+ if (status === "passed" && (personas.length === 0 || scenarios.length === 0)) {
560
+ return failedHostActorPlan({
561
+ repo,
562
+ status: "failed",
563
+ summary: "Host Codex actor plan lacked usable personas or scenarios."
564
+ });
565
+ }
566
+ return {
567
+ schema: "mimetic.oss-host-actor-plan.v1",
568
+ generatedAt: new Date().toISOString(),
569
+ personas,
570
+ recommendedProof: normalizeHostActorRecommendedProof(parsed.recommendedProof),
571
+ repo,
572
+ scenarios,
573
+ source: "local-codex-exec",
574
+ status,
575
+ summary: cleanHostActorText(parsed.summary, status === "passed" ? "Host Codex actor authored a public-safe Mimetic plan." : "Host Codex actor could not author a complete plan.")
576
+ };
577
+ }
578
+ function normalizeHostActorPersona(value) {
579
+ if (!value || typeof value !== "object")
580
+ return null;
581
+ const candidate = value;
582
+ const id = safeArtifactToken(cleanHostActorText(candidate.id, "host-actor-persona")).slice(0, 80) || "host-actor-persona";
583
+ const traits = Array.isArray(candidate.traits)
584
+ ? candidate.traits.map((trait) => cleanHostActorText(trait, "")).filter(Boolean).slice(0, 5)
585
+ : [];
586
+ return {
587
+ id,
588
+ name: cleanHostActorText(candidate.name, "Host Actor Persona"),
589
+ intent: cleanHostActorText(candidate.intent, "Evaluate the app with a public-safe synthetic goal."),
590
+ traits: traits.length > 0 ? traits : ["public_safe", "synthetic_user"]
591
+ };
592
+ }
593
+ function normalizeHostActorScenario(value) {
594
+ if (!value || typeof value !== "object")
595
+ return null;
596
+ const candidate = value;
597
+ const steps = Array.isArray(candidate.steps)
598
+ ? candidate.steps.map((step) => cleanHostActorText(step, "")).filter(Boolean).slice(0, 8)
599
+ : [];
600
+ if (steps.length === 0)
601
+ return null;
602
+ return {
603
+ id: safeArtifactToken(cleanHostActorText(candidate.id, "host-actor-scenario")).slice(0, 80) || "host-actor-scenario",
604
+ title: cleanHostActorText(candidate.title, "Host Actor Scenario"),
605
+ goal: cleanHostActorText(candidate.goal, "Exercise the primary public-safe app workflow."),
606
+ steps
607
+ };
608
+ }
609
+ export function normalizeHostActorRecommendedProof(value) {
610
+ const proof = cleanHostActorText(value, "");
611
+ if (!/\bmimetic\s+run\b/.test(proof)) {
612
+ return "Start the target app on a loopback URL, then run `mimetic run --app-url http://127.0.0.1:<port> --sims 2`.";
613
+ }
614
+ if (/\s--(?:browser|viewport|persona|scenario)\b/.test(proof)) {
615
+ return "Start the target app on a loopback URL, then run `mimetic run --app-url http://127.0.0.1:<port> --sims 2`.";
616
+ }
617
+ return proof;
618
+ }
619
+ function failedHostActorPlan(args) {
620
+ return {
621
+ schema: "mimetic.oss-host-actor-plan.v1",
622
+ generatedAt: new Date().toISOString(),
623
+ personas: [],
624
+ recommendedProof: "Host actor plan was not available.",
625
+ repo: args.repo,
626
+ scenarios: [],
627
+ source: "local-codex-exec",
628
+ status: args.status,
629
+ summary: cleanHostActorText(args.summary, "Host Codex actor plan failed.")
630
+ };
631
+ }
632
+ function parseJsonObject(raw) {
633
+ for (const line of raw.split(/\r?\n/).map((value) => value.trim()).filter(Boolean).reverse()) {
634
+ if (line.startsWith("{") && line.endsWith("}")) {
635
+ try {
636
+ const parsed = JSON.parse(line);
637
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
638
+ return parsed;
639
+ }
640
+ catch { }
641
+ }
642
+ }
643
+ const fence = /```(?:json)?\s*([\s\S]*?)```/i.exec(raw);
644
+ if (fence?.[1]) {
645
+ try {
646
+ const parsed = JSON.parse(fence[1].trim());
647
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
648
+ return parsed;
649
+ }
650
+ catch { }
651
+ }
652
+ try {
653
+ const parsed = JSON.parse(raw);
654
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
655
+ }
656
+ catch {
657
+ const lastClose = raw.lastIndexOf("}");
658
+ if (lastClose === -1)
659
+ return null;
660
+ for (let start = raw.lastIndexOf("{", lastClose); start >= 0; start = raw.lastIndexOf("{", start - 1)) {
661
+ try {
662
+ const parsed = JSON.parse(raw.slice(start, lastClose + 1));
663
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
664
+ return parsed;
665
+ }
666
+ catch { }
667
+ }
668
+ return null;
669
+ }
670
+ }
671
+ function cleanHostActorText(value, fallback) {
672
+ const text = String(typeof value === "string" ? value : fallback)
673
+ .replace(/sk-[A-Za-z0-9_-]{20,}/g, "[redacted-openai-key]")
674
+ .replace(/e2b_[A-Za-z0-9_-]{12,}/g, "[redacted-e2b-key]")
675
+ .replace(/gh[pousr]_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
676
+ .replace(/github_pat_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
677
+ .replace(/\s+/g, " ")
678
+ .trim()
679
+ .slice(0, 500);
680
+ return text || fallback;
681
+ }
28
682
  export async function runOssMetaLab(options) {
29
683
  const cwd = path.resolve(options.cwd);
30
684
  const dryRun = options.dryRun === true;
@@ -32,7 +686,7 @@ export async function runOssMetaLab(options) {
32
686
  const warnings = [];
33
687
  const repos = normalizeOssRepoSlugs(options.repos);
34
688
  const count = options.count ?? DEFAULT_OSS_REPOS.length;
35
- if (!Number.isInteger(count) || count < 1 || count > 16) {
689
+ if (!Number.isInteger(count) || count < 1) {
36
690
  return {
37
691
  schema: OSS_META_LAB_SCHEMA,
38
692
  ok: false,
@@ -41,7 +695,7 @@ export async function runOssMetaLab(options) {
41
695
  dryRun,
42
696
  error: {
43
697
  code: "MIMETIC_INVALID_OSS_COUNT",
44
- message: "--count must be an integer between 1 and 16."
698
+ message: "--count must be a positive integer."
45
699
  },
46
700
  liveRequested,
47
701
  repos,
@@ -60,7 +714,7 @@ export async function runOssMetaLab(options) {
60
714
  dryRun,
61
715
  error: {
62
716
  code: "MIMETIC_INVALID_OSS_REPO",
63
- message: `Only public GitHub owner/repo slugs are supported: ${invalid}`
717
+ message: `Only GitHub owner/repo slugs are supported: ${invalid}`
64
718
  },
65
719
  liveRequested,
66
720
  repos,
@@ -68,15 +722,128 @@ export async function runOssMetaLab(options) {
68
722
  warnings
69
723
  };
70
724
  }
725
+ const redactRepoNames = options.redactRepoNames ?? (liveRequested && (Boolean(githubTokenFromEnv(process.env)) || Boolean(options.repos?.length)));
726
+ const hostActorMode = liveRequested && hostCodexActorRequested(process.env);
71
727
  const missingKeys = missingLiveKeys(process.env);
72
728
  if (liveRequested && missingKeys.length > 0) {
73
729
  warnings.push(`Live E2B/Codex launch is waiting on env vars: ${missingKeys.join(", ")}.`);
74
730
  warnings.push("Observer lanes stay in the live waiting state until keys are present.");
75
731
  }
732
+ if (liveRequested && !githubTokenFromEnv(process.env)) {
733
+ warnings.push("No GH_TOKEN, GITHUB_TOKEN, or GITHUB_PAT is present; public repos can clone, but private GitHub repos will fail access preflight.");
734
+ }
76
735
  const assignments = buildOssRepoAssignments(repos, count);
736
+ const publicAssignments = redactAssignments(assignments, redactRepoNames);
737
+ const publicRepos = redactRepoNames ? publicAssignments.map((assignment) => assignment.repo) : repos;
77
738
  const runId = options.runId ?? makeMetaRunId();
739
+ const runResult = await runDryRun({
740
+ cwd,
741
+ dryRun: true,
742
+ runId,
743
+ simCount: count
744
+ });
745
+ if (!runResult.ok || !runResult.runId) {
746
+ return {
747
+ schema: OSS_META_LAB_SCHEMA,
748
+ ok: false,
749
+ assignments: publicAssignments,
750
+ count,
751
+ cwd,
752
+ dryRun,
753
+ error: {
754
+ code: "MIMETIC_META_RUN_FAILED",
755
+ message: runResult.error?.message ?? "Failed to create OSS meta-lab run bundle."
756
+ },
757
+ liveRequested,
758
+ repos: publicRepos,
759
+ sandboxes: [],
760
+ warnings: [...warnings, ...runResult.warnings]
761
+ };
762
+ }
763
+ const artifactRoot = path.join(cwd, ".mimetic", "runs", runId);
764
+ const createdAt = new Date().toISOString();
765
+ const persistScreenshots = !redactRepoNames;
766
+ let liveDesktops = [];
767
+ let substrateMissingKeys = [...missingKeys];
768
+ await mkdir(artifactRoot, { recursive: true });
769
+ const initialBundle = buildMetaBundle({
770
+ assignments,
771
+ createdAt,
772
+ cwd,
773
+ dryRun,
774
+ liveDesktops,
775
+ liveRequested,
776
+ missingKeys: substrateMissingKeys,
777
+ redactRepoNames,
778
+ runId
779
+ });
780
+ await writeMetaBundleArtifacts(artifactRoot, initialBundle);
781
+ let observer = await renderObserver(cwd, runId, { open: false });
782
+ if (observer.ok && options.onObserverReady) {
783
+ await options.onObserverReady(observer);
784
+ }
785
+ const shouldPreflightRepoAccess = liveRequested
786
+ && missingKeys.length === 0
787
+ && process.env.MIMETIC_OSS_META_SKIP_REPO_ACCESS_PREFLIGHT !== "1"
788
+ && (Boolean(githubTokenFromEnv(process.env)) || Boolean(options.repos?.length));
789
+ const repoAccessPreflight = shouldPreflightRepoAccess
790
+ ? await preflightOssMetaRepoAccess({
791
+ assignments,
792
+ cwd,
793
+ env: process.env,
794
+ redactRepoNames
795
+ })
796
+ : [];
797
+ const repoAccessPreflightBlocked = repoAccessPreflight.some((result) => !result.ok);
798
+ if (repoAccessPreflight.length > 0) {
799
+ const passed = repoAccessPreflight.filter((result) => result.ok).length;
800
+ warnings.push(`GitHub repo clone access preflight passed ${passed}/${repoAccessPreflight.length} assigned repo${repoAccessPreflight.length === 1 ? "" : "s"}.`);
801
+ for (const failed of repoAccessPreflight.filter((result) => !result.ok)) {
802
+ const assignment = assignments.find((candidate) => candidate.streamId === failed.streamId);
803
+ const repoLabel = assignment && redactRepoNames ? repoArtifactLabel(assignment) : failed.repo;
804
+ warnings.push(`${repoLabel}: ${failed.reason}`);
805
+ }
806
+ }
807
+ const hostActorPlanResults = hostActorMode && missingKeys.length === 0 && !repoAccessPreflightBlocked
808
+ ? await createHostActorPlans({
809
+ assignments,
810
+ cwd,
811
+ redactRepoNames,
812
+ runId
813
+ })
814
+ : [];
815
+ const hostActorPlansByStream = new Map(hostActorPlanResults.flatMap((result) => result.plan?.status === "passed" ? [[result.streamId, result]] : []));
816
+ if (hostActorPlanResults.length > 0) {
817
+ const passed = hostActorPlanResults.filter((result) => result.plan?.status === "passed").length;
818
+ warnings.push(`Host Codex actor authored ${passed}/${hostActorPlanResults.length} public-safe Mimetic plan${hostActorPlanResults.length === 1 ? "" : "s"}.`);
819
+ for (const failed of hostActorPlanResults.filter((result) => result.plan?.status !== "passed")) {
820
+ warnings.push(`Host Codex actor plan failed for ${redactRepoNames ? "[redacted-authorized-repo]" : failed.repo}: ${failed.error ?? failed.plan?.summary ?? "unknown failure"}`);
821
+ }
822
+ }
823
+ const hostActorPlanBlocked = hostActorMode
824
+ && actorRequired(process.env)
825
+ && hostActorPlanResults.length > 0
826
+ && hostActorPlanResults.some((result) => result.plan?.status !== "passed");
827
+ const actorAuthPreflight = liveRequested
828
+ && missingKeys.length === 0
829
+ && !hostActorMode
830
+ && actorRequired(process.env)
831
+ && process.env.MIMETIC_OSS_META_SKIP_ACTOR_PREFLIGHT !== "1"
832
+ ? await preflightOssMetaActorApiKey({ env: process.env })
833
+ : undefined;
834
+ const actorAuthPreflightBlocked = actorAuthPreflight !== undefined && !actorAuthPreflight.ok;
835
+ substrateMissingKeys = [
836
+ ...missingKeys,
837
+ ...(hostActorPlanBlocked ? [OSS_META_LAB_HOST_ACTOR_PLACEHOLDER] : []),
838
+ ...(actorAuthPreflightBlocked ? [OSS_META_LAB_ACTOR_PREFLIGHT_PLACEHOLDER] : [])
839
+ ];
840
+ if (actorAuthPreflight) {
841
+ warnings.push(actorAuthPreflight.ok
842
+ ? actorAuthPreflight.reason
843
+ : `Remote Codex actor API-key preflight blocked live launch: ${actorAuthPreflight.reason}`);
844
+ }
78
845
  let localPackage;
79
- if (liveRequested && missingKeys.length === 0) {
846
+ if (liveRequested && missingKeys.length === 0 && !repoAccessPreflightBlocked && !hostActorPlanBlocked && !actorAuthPreflightBlocked) {
80
847
  try {
81
848
  localPackage = await packLocalMimeticPackage(cwd, runId);
82
849
  warnings.push(`Packed local mimetic-cli package for sandbox install (${localPackage.fileName}).`);
@@ -85,33 +852,67 @@ export async function runOssMetaLab(options) {
85
852
  warnings.push(`Local mimetic-cli package pack failed; sandbox bootstrap will try public npm fallback. ${compactError(error)}`);
86
853
  }
87
854
  }
88
- let liveDesktops = [];
89
- if (liveRequested && missingKeys.length === 0) {
855
+ if (liveRequested && missingKeys.length === 0 && !repoAccessPreflightBlocked && !hostActorPlanBlocked && !actorAuthPreflightBlocked) {
90
856
  try {
91
- liveDesktops = await launchLiveDesktops(assignments, localPackage ? { localPackage } : {});
92
- const completionSummary = await pollLiveDesktopCompletions(liveDesktops);
857
+ liveDesktops = await launchLiveDesktops(assignments, {
858
+ cwd,
859
+ hostActorPlansByStream,
860
+ ...(localPackage ? { localPackage } : {}),
861
+ redactRepoNames
862
+ });
863
+ const completionSummary = await pollLiveDesktopCompletions(liveDesktops, {
864
+ ...(options.completionTimeoutMs === undefined
865
+ ? {}
866
+ : {
867
+ timeoutMs: options.completionTimeoutMs,
868
+ timeoutReason: "attached watch mode serves the Observer immediately after desktop streams are created"
869
+ })
870
+ });
93
871
  warnings.push(...completionSummary.warnings);
94
872
  }
95
873
  catch (error) {
96
874
  warnings.push(compactError(error));
97
- liveDesktops = assignments.map((assignment) => ({
98
- error: compactError(error),
99
- repo: assignment.repo,
100
- simId: assignment.simId,
101
- streamId: assignment.streamId
102
- }));
875
+ liveDesktops = assignments.map((assignment) => {
876
+ const hostActorPlanResult = hostActorPlanResults.find((result) => result.streamId === assignment.streamId);
877
+ return {
878
+ error: compactError(error),
879
+ ...(hostActorPlanResult?.plan ? { hostActorPlan: hostActorPlanResult.plan } : {}),
880
+ ...(hostActorPlanResult?.artifactPath ? { hostActorPlanPath: hostActorPlanResult.artifactPath } : {}),
881
+ repo: redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo,
882
+ simId: assignment.simId,
883
+ streamId: assignment.streamId
884
+ };
885
+ });
103
886
  }
104
887
  }
888
+ else if (repoAccessPreflightBlocked) {
889
+ liveDesktops = blockedLiveDesktopsForRepoAccess({
890
+ assignments,
891
+ preflight: repoAccessPreflight,
892
+ redactRepoNames
893
+ });
894
+ warnings.push("Live E2B launch skipped because GitHub repo clone access preflight failed.");
895
+ }
896
+ else if (hostActorPlanBlocked) {
897
+ warnings.push("Live E2B launch skipped because required host Codex actor plan evidence did not pass preflight.");
898
+ }
899
+ else if (actorAuthPreflightBlocked) {
900
+ warnings.push("Live E2B launch skipped because required Codex actor API-key quota/auth preflight failed.");
901
+ }
105
902
  const liveDesktopCount = liveDesktops.filter((desktop) => desktop.url).length;
106
903
  const failedLiveDesktopCount = liveDesktops.filter((desktop) => desktop.error).length;
107
904
  const startedBootstrapCount = liveDesktops.filter((desktop) => desktop.bootstrap?.status === "started").length;
108
905
  const terminalCompletionCount = liveDesktops.filter((desktop) => isTerminalCompletion(desktop.completion)).length;
906
+ const runningAppCount = liveDesktops.filter((desktop) => desktop.completion?.appStatus === "running").length;
907
+ const visibleDesktopCount = liveDesktops.filter((desktop) => desktop.completion?.visualStatus === "visible").length;
109
908
  if (liveDesktops.length > 0) {
110
909
  warnings.push(`Launched ${liveDesktopCount}/${liveDesktops.length} live E2B desktop stream${liveDesktops.length === 1 ? "" : "s"}.`);
111
910
  if (startedBootstrapCount > 0) {
112
- warnings.push(`Started ${startedBootstrapCount}/${liveDesktops.length} visible bootstrap terminal${liveDesktops.length === 1 ? "" : "s"} for Codex TUI attempt and nested Mimetic setup.`);
911
+ warnings.push(`Started ${startedBootstrapCount}/${liveDesktops.length} visible bootstrap terminal${liveDesktops.length === 1 ? "" : "s"} for target app startup, nested Mimetic setup, and Codex actor attempt.`);
113
912
  if (terminalCompletionCount > 0) {
114
913
  warnings.push(`Classified ${terminalCompletionCount}/${startedBootstrapCount} bootstrap terminal state${startedBootstrapCount === 1 ? "" : "s"} from remote public-safe evidence.`);
914
+ warnings.push(`Detected ${runningAppCount}/${terminalCompletionCount} target app HTTP-ready surface${terminalCompletionCount === 1 ? "" : "s"} from remote public-safe evidence.`);
915
+ warnings.push(`Detected ${visibleDesktopCount}/${terminalCompletionCount} headed desktop visual layout${terminalCompletionCount === 1 ? "" : "s"} from remote public-safe evidence.`);
115
916
  }
116
917
  }
117
918
  else {
@@ -121,36 +922,18 @@ export async function runOssMetaLab(options) {
121
922
  if (failedLiveDesktopCount > 0) {
122
923
  warnings.push(`${failedLiveDesktopCount} E2B desktop launch${failedLiveDesktopCount === 1 ? "" : "es"} failed; see stream events in the Observer.`);
123
924
  }
124
- const runResult = await runDryRun({
125
- cwd,
126
- dryRun: true,
127
- runId,
128
- simCount: count
129
- });
130
- if (!runResult.ok || !runResult.runId) {
131
- return {
132
- schema: OSS_META_LAB_SCHEMA,
133
- ok: false,
134
- assignments,
135
- count,
136
- cwd,
137
- dryRun,
138
- error: {
139
- code: "MIMETIC_META_RUN_FAILED",
140
- message: runResult.error?.message ?? "Failed to create OSS meta-lab run bundle."
141
- },
142
- liveRequested,
143
- repos,
144
- sandboxes: liveDesktops.map(formatLiveDesktopForResult),
145
- warnings: [...warnings, ...runResult.warnings]
146
- };
925
+ if (persistScreenshots) {
926
+ const screenshotSummary = await captureLiveDesktopScreenshots(artifactRoot, liveDesktops);
927
+ warnings.push(...screenshotSummary.warnings);
147
928
  }
148
- const artifactRoot = path.join(cwd, ".mimetic", "runs", runId);
149
- const bundlePath = path.join(artifactRoot, "run.json");
150
- const createdAt = new Date().toISOString();
151
- await mkdir(artifactRoot, { recursive: true });
152
- const screenshotSummary = await captureLiveDesktopScreenshots(artifactRoot, liveDesktops);
153
- warnings.push(...screenshotSummary.warnings);
929
+ else if (liveDesktops.some((desktop) => desktop.url)) {
930
+ warnings.push("Skipped persistent desktop screenshots because repo labels are redacted; live stream URLs remain runtime-only.");
931
+ }
932
+ const actorEvidenceSummary = await writeActorEvidenceArtifacts(artifactRoot, liveDesktops, {
933
+ assignments,
934
+ redactRepoNames
935
+ });
936
+ warnings.push(...actorEvidenceSummary.warnings);
154
937
  const bundle = buildMetaBundle({
155
938
  assignments,
156
939
  createdAt,
@@ -158,24 +941,31 @@ export async function runOssMetaLab(options) {
158
941
  dryRun,
159
942
  liveDesktops,
160
943
  liveRequested,
161
- missingKeys,
944
+ missingKeys: substrateMissingKeys,
945
+ redactRepoNames,
162
946
  runId
163
947
  });
164
- await writeJson(bundlePath, bundle);
165
- await writeJson(path.join(artifactRoot, "review.json"), bundle.review);
166
- await writeFile(path.join(artifactRoot, "review.md"), renderMetaReviewMarkdown(bundle), "utf8");
167
- await writeFile(path.join(artifactRoot, "events.ndjson"), `${bundle.events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
168
- const observer = await renderObserver(cwd, runId, { open: options.open === true });
948
+ await writeMetaBundleArtifacts(artifactRoot, bundle);
949
+ const finalObserver = await renderObserver(cwd, runId, { open: options.open === true });
950
+ Object.assign(observer, finalObserver);
951
+ if (observer.ok) {
952
+ attachObserverRuntimeStreamUrls(observer, liveDesktops
953
+ .filter((desktop) => desktop.url)
954
+ .map((desktop) => ({
955
+ streamId: desktop.streamId,
956
+ url: desktop.url
957
+ })));
958
+ }
169
959
  const outcome = classifyMetaLabOutcome({
170
960
  dryRun,
171
961
  liveDesktops,
172
962
  liveRequested,
173
- missingKeys
963
+ missingKeys: substrateMissingKeys
174
964
  });
175
- return {
965
+ const result = {
176
966
  schema: OSS_META_LAB_SCHEMA,
177
967
  ok: observer.ok && outcome.ok,
178
- assignments,
968
+ assignments: publicAssignments,
179
969
  count,
180
970
  cwd,
181
971
  dryRun,
@@ -189,11 +979,157 @@ export async function runOssMetaLab(options) {
189
979
  }),
190
980
  liveRequested,
191
981
  observer,
192
- repos,
982
+ repos: publicRepos,
193
983
  runId,
194
- sandboxes: liveDesktops.map(formatLiveDesktopForResult),
984
+ sandboxes: liveDesktops.map((desktop) => formatLiveDesktopForResult(desktop, redactRepoNames)),
195
985
  warnings: [...warnings, ...observer.warnings]
196
986
  };
987
+ if (observer.ok && liveDesktops.some((desktop) => desktop.desktop && desktop.bootstrap?.status === "started")) {
988
+ liveRuntimeByResult.set(result, {
989
+ artifactRoot,
990
+ assignments,
991
+ createdAt,
992
+ cwd,
993
+ dryRun,
994
+ liveDesktops,
995
+ liveRequested,
996
+ missingKeys: substrateMissingKeys,
997
+ persistScreenshots,
998
+ redactRepoNames,
999
+ runId,
1000
+ startedAt: Date.now()
1001
+ });
1002
+ }
1003
+ return result;
1004
+ }
1005
+ async function writeMetaBundleArtifacts(artifactRoot, bundle) {
1006
+ await writeJson(path.join(artifactRoot, "run.json"), bundle);
1007
+ await writeJson(path.join(artifactRoot, "review.json"), bundle.review);
1008
+ await writeFile(path.join(artifactRoot, "review.md"), renderMetaReviewMarkdown(bundle), "utf8");
1009
+ await writeFile(path.join(artifactRoot, "events.ndjson"), `${bundle.events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
1010
+ }
1011
+ export function startOssMetaLabLiveRefresh(result, options = {}) {
1012
+ const runtime = liveRuntimeByResult.get(result);
1013
+ if (!runtime) {
1014
+ return null;
1015
+ }
1016
+ const intervalMs = options.intervalMs ?? readPositiveInt(process.env.MIMETIC_OSS_META_WATCH_REFRESH_MS, 5_000);
1017
+ const screenshotIntervalMs = options.screenshotIntervalMs ?? readPositiveInt(process.env.MIMETIC_OSS_META_SCREENSHOT_REFRESH_MS, 15_000);
1018
+ const timeoutMs = options.timeoutMs ?? readNonNegativeInt(process.env.MIMETIC_OSS_META_COMPLETION_TIMEOUT_MS, 240_000);
1019
+ const deadline = timeoutMs === 0 ? null : runtime.startedAt + timeoutMs;
1020
+ let lastScreenshotAt = 0;
1021
+ let timer = null;
1022
+ let stopped = false;
1023
+ let running = false;
1024
+ const tick = async () => {
1025
+ if (stopped || running) {
1026
+ return;
1027
+ }
1028
+ running = true;
1029
+ try {
1030
+ const now = Date.now();
1031
+ const timedOut = deadline !== null && now >= deadline;
1032
+ const shouldCaptureScreenshot = runtime.persistScreenshots
1033
+ && (timedOut || lastScreenshotAt === 0 || now - lastScreenshotAt >= screenshotIntervalMs);
1034
+ await refreshOssMetaLabLiveRuntime(runtime, {
1035
+ captureScreenshots: shouldCaptureScreenshot,
1036
+ timedOut,
1037
+ timeoutMs
1038
+ });
1039
+ result.sandboxes = runtime.liveDesktops.map((desktop) => formatLiveDesktopForResult(desktop, runtime.redactRepoNames));
1040
+ const outcome = classifyMetaLabOutcome({
1041
+ dryRun: runtime.dryRun,
1042
+ liveDesktops: runtime.liveDesktops,
1043
+ liveRequested: runtime.liveRequested,
1044
+ missingKeys: runtime.missingKeys
1045
+ });
1046
+ result.ok = result.observer?.ok === true && outcome.ok;
1047
+ if (result.ok) {
1048
+ delete result.error;
1049
+ }
1050
+ else {
1051
+ result.error = {
1052
+ code: "MIMETIC_META_RUN_FAILED",
1053
+ message: outcome.reason
1054
+ };
1055
+ }
1056
+ if (shouldCaptureScreenshot) {
1057
+ lastScreenshotAt = now;
1058
+ }
1059
+ if (runtime.liveDesktops.every((desktop) => isTerminalCompletion(desktop.completion))) {
1060
+ if (timer) {
1061
+ clearInterval(timer);
1062
+ timer = null;
1063
+ }
1064
+ }
1065
+ }
1066
+ catch {
1067
+ // Attached watch is best-effort telemetry. Keep serving the last verified
1068
+ // bundle instead of crashing the user's observer process.
1069
+ }
1070
+ finally {
1071
+ running = false;
1072
+ }
1073
+ };
1074
+ void tick();
1075
+ timer = setInterval(() => {
1076
+ void tick();
1077
+ }, intervalMs);
1078
+ return {
1079
+ async stop() {
1080
+ stopped = true;
1081
+ if (timer) {
1082
+ clearInterval(timer);
1083
+ timer = null;
1084
+ }
1085
+ while (running) {
1086
+ await wait(100);
1087
+ }
1088
+ }
1089
+ };
1090
+ }
1091
+ export function sandboxIdsForOssMetaLabCleanup(result) {
1092
+ return [...new Set(result.sandboxes.flatMap((sandbox) => sandbox.sandboxId ? [sandbox.sandboxId] : []))];
1093
+ }
1094
+ export async function cleanupOssMetaLabSandboxes(result, options = {}) {
1095
+ const ids = sandboxIdsForOssMetaLabCleanup(result);
1096
+ const skipped = result.sandboxes.length - ids.length;
1097
+ if (ids.length === 0) {
1098
+ return { killed: 0, skipped, errors: [] };
1099
+ }
1100
+ const requestTimeoutMs = options.requestTimeoutMs ?? readPositiveInt(process.env.MIMETIC_E2B_REQUEST_TIMEOUT_MS, 60_000);
1101
+ let killSandbox = options.killSandbox;
1102
+ if (!killSandbox) {
1103
+ const e2bApiKey = process.env.E2B_API_KEY;
1104
+ if (!e2bApiKey) {
1105
+ return {
1106
+ killed: 0,
1107
+ skipped: skipped + ids.length,
1108
+ errors: ["E2B_API_KEY is not present; remote sandbox cleanup skipped."]
1109
+ };
1110
+ }
1111
+ const desktopModule = await loadE2BDesktopModule();
1112
+ if (!desktopModule.Sandbox.kill) {
1113
+ return {
1114
+ killed: 0,
1115
+ skipped: skipped + ids.length,
1116
+ errors: ["Installed @e2b/desktop SDK does not expose Sandbox.kill; remote sandbox cleanup skipped."]
1117
+ };
1118
+ }
1119
+ killSandbox = async (sandboxId, timeout) => desktopModule.Sandbox.kill?.(sandboxId, { requestTimeoutMs: timeout });
1120
+ }
1121
+ let killed = 0;
1122
+ const errors = [];
1123
+ for (const id of ids) {
1124
+ try {
1125
+ await killSandbox(id, requestTimeoutMs);
1126
+ killed += 1;
1127
+ }
1128
+ catch (error) {
1129
+ errors.push(`${id}: ${compactError(error)}`);
1130
+ }
1131
+ }
1132
+ return { killed, skipped, errors };
197
1133
  }
198
1134
  export function buildOssMetaBundleFixture(args) {
199
1135
  return buildMetaBundle({
@@ -204,9 +1140,26 @@ export function buildOssMetaBundleFixture(args) {
204
1140
  liveDesktops: args.lanes,
205
1141
  liveRequested: args.liveRequested,
206
1142
  missingKeys: args.missingKeys,
1143
+ redactRepoNames: args.redactRepoNames === true,
207
1144
  runId: args.runId
208
1145
  });
209
1146
  }
1147
+ export function buildOssMetaBootstrapScriptFixture() {
1148
+ const [assignment] = buildOssRepoAssignments(["maciekt07/TodoApp"], 1);
1149
+ if (!assignment) {
1150
+ throw new Error("Missing OSS meta-lab fixture assignment.");
1151
+ }
1152
+ return buildRemoteBootstrapScript({
1153
+ assignment,
1154
+ completionPath: "/home/user/mimetic-oss-lab/maciekt07-todoapp/completion.json",
1155
+ displayRepo: "maciekt07/TodoApp",
1156
+ logPath: "/home/user/mimetic-oss-lab/maciekt07-todoapp/bootstrap.log",
1157
+ nestedObserverPath: "/home/user/mimetic-oss-lab/maciekt07-todoapp/repo/.mimetic/runs/nested-maciekt07-todoapp/observer/index.html",
1158
+ remoteHostActorPlanPath: "/home/user/mimetic-oss-lab/maciekt07-todoapp/host-actor-plan.json",
1159
+ rootDir: "/home/user/mimetic-oss-lab/maciekt07-todoapp",
1160
+ token: "maciekt07-todoapp"
1161
+ });
1162
+ }
210
1163
  function buildMetaBundle(args) {
211
1164
  const simulations = [];
212
1165
  const streams = [];
@@ -220,27 +1173,33 @@ function buildMetaBundle(args) {
220
1173
  }
221
1174
  ];
222
1175
  for (const assignment of args.assignments) {
223
- const prompt = buildCodexBootstrapPrompt(assignment);
224
- const liveDesktop = args.liveDesktops.find((desktop) => desktop.streamId === assignment.streamId);
1176
+ const prompt = buildCodexBootstrapPrompt(assignment, args.redactRepoNames);
1177
+ const rawLiveDesktop = args.liveDesktops.find((desktop) => desktop.streamId === assignment.streamId);
1178
+ const liveDesktop = rawLiveDesktop && args.redactRepoNames
1179
+ ? redactLiveDesktopRepoMentions(rawLiveDesktop, assignment.repo)
1180
+ : rawLiveDesktop;
1181
+ const repoLabel = args.redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo;
1182
+ const scenarioId = args.redactRepoNames ? `oss-meta-${repoLabel}` : assignment.scenarioId;
225
1183
  const status = statusForMeta(args, liveDesktop);
226
1184
  const completion = liveDesktop?.completion;
227
1185
  const screenshot = liveDesktop?.screenshot;
228
1186
  const terminalTail = terminalTailForMeta(prompt, liveDesktop);
1187
+ const liveStreamPresent = Boolean(liveDesktop?.url);
229
1188
  simulations.push({
230
1189
  id: assignment.simId,
231
1190
  index: assignment.index,
232
1191
  personaId: `codex-oss-operator-${String(assignment.index).padStart(2, "0")}`,
233
- scenarioId: assignment.scenarioId,
1192
+ scenarioId,
234
1193
  status,
235
1194
  streamKind: "browser",
236
1195
  mode: "browser-sim",
237
1196
  progress: progressForMeta(status, liveDesktop),
238
- currentStep: currentStepForMeta(args, assignment),
1197
+ currentStep: currentStepForMeta(args, assignment, liveDesktop),
239
1198
  summary: completion
240
- ? `Headed E2B desktop lane assigned to ${assignment.repo}; ${completion.reason}`
1199
+ ? `Headed E2B desktop lane assigned to ${repoLabel}; ${completion.reason}`
241
1200
  : liveDesktop?.bootstrap?.status === "started"
242
- ? `Headed E2B desktop lane assigned to ${assignment.repo}; bootstrap terminal launched to set up Mimetic and open the nested Observer.`
243
- : `Headed E2B desktop lane assigned to ${assignment.repo}; nested Codex TUI should set up Mimetic and open a nested Observer inside that desktop.`,
1201
+ ? `Headed E2B desktop lane assigned to ${repoLabel}; bootstrap terminal launched to set up Mimetic and open the nested Observer.`
1202
+ : `Headed E2B desktop lane assigned to ${repoLabel}; nested Codex TUI should set up Mimetic and open a nested Observer inside that desktop.`,
244
1203
  streamIds: [assignment.streamId],
245
1204
  startedAt: args.createdAt,
246
1205
  updatedAt: args.createdAt
@@ -249,14 +1208,13 @@ function buildMetaBundle(args) {
249
1208
  id: assignment.streamId,
250
1209
  simId: assignment.simId,
251
1210
  kind: "browser",
252
- label: `E2B desktop - ${assignment.repo}`,
1211
+ label: `E2B desktop - ${repoLabel}`,
253
1212
  status,
254
- transport: screenshot ? "snapshot" : liveDesktop?.url ? "sse" : status === "contract_proof_only" ? "snapshot" : "sse",
1213
+ transport: liveStreamPresent ? "sse" : screenshot ? "snapshot" : status === "contract_proof_only" ? "snapshot" : "sse",
255
1214
  updatedAt: args.createdAt,
256
- ...(liveDesktop?.url && !screenshot ? { url: liveDesktop.url } : {}),
257
1215
  embed: {
258
- kind: screenshot ? "screenshot" : liveDesktop?.url ? "iframe" : "placeholder",
259
- ...(liveDesktop?.url && !screenshot ? { url: liveDesktop.url } : {}),
1216
+ kind: screenshot ? "screenshot" : "placeholder",
1217
+ ...(screenshot ? { url: screenshot.observerUrl } : {}),
260
1218
  title: `E2B desktop ${assignment.index}`
261
1219
  },
262
1220
  viewport: {
@@ -265,26 +1223,42 @@ function buildMetaBundle(args) {
265
1223
  deviceScaleFactor: 1
266
1224
  },
267
1225
  terminal: {
268
- title: `Codex TUI bootstrap - ${assignment.repo}`,
1226
+ title: `Codex TUI bootstrap - ${repoLabel}`,
269
1227
  format: "plain",
270
1228
  stdin: liveDesktop?.bootstrap ? "sent" : "planned",
271
1229
  tail: terminalTail
272
1230
  },
273
1231
  ui: {
274
- route: `e2b://desktop/${assignment.repo}`,
275
- intent: "Watch the headed desktop where Codex clones the repo, sets up Mimetic, and opens the nested Observer.",
1232
+ route: completion?.appUrl ?? `e2b://desktop/${repoLabel}`,
1233
+ intent: "Watch the headed desktop where the bootstrap clones the repo, starts the target app, sets up Mimetic, opens the nested Observer, and attempts a Codex actor.",
1234
+ ...(completion?.actorStatus ? { actorStatus: completion.actorStatus } : {}),
1235
+ ...(completion?.appStatus ? { appStatus: completion.appStatus } : {}),
1236
+ ...(completion?.appUrl ? { appUrl: completion.appUrl } : {}),
1237
+ ...(liveDesktop?.bootstrap?.nestedObserverPath ? { nestedObserverPath: liveDesktop.bootstrap.nestedObserverPath } : {}),
276
1238
  ...(screenshot ? { screenshotUrl: screenshot.observerUrl } : {}),
1239
+ ...(completion?.visualStatus ? { visualStatus: completion.visualStatus } : {}),
277
1240
  state: completion
278
- ? completion.reason
1241
+ ? [
1242
+ completion.reason,
1243
+ completion.appStatus ? `app=${completion.appStatus}` : "",
1244
+ completion.actorStatus ? `actor=${completion.actorStatus}` : "",
1245
+ completion.visualStatus ? `visual=${completion.visualStatus}` : ""
1246
+ ].filter(Boolean).join(" | ")
279
1247
  : liveDesktop?.bootstrap?.status === "started"
280
- ? "bootstrap terminal launched"
281
- : liveDesktop?.url ? "live E2B desktop" : args.dryRun ? "contract desktop" : "headed E2B desktop"
1248
+ ? "bootstrap terminal launched; target app and nested Observer setup running"
1249
+ : liveStreamPresent ? "live E2B desktop stream present; stream URL is runtime-only" : args.dryRun ? "contract desktop" : "headed E2B desktop"
282
1250
  },
283
1251
  ...(completion ? { completion: completionForStream(completion) } : {}),
284
1252
  artifacts: [
285
1253
  { label: "run bundle", path: "run.json", kind: "bundle" },
286
1254
  { label: "review", path: "review.md", kind: "review" },
287
1255
  { label: "events", path: "events.ndjson", kind: "events" },
1256
+ ...(completion?.appLogPath ? [{ label: "remote app log", path: completion.appLogPath, kind: "log" }] : []),
1257
+ ...(completion?.actorLogPath ? [{ label: "remote actor log", path: completion.actorLogPath, kind: "log" }] : []),
1258
+ ...(liveDesktop?.actorEvidence?.actorLastMessageTailPath ? [{ label: "actor last-message tail", path: liveDesktop.actorEvidence.actorLastMessageTailPath, kind: "log" }] : []),
1259
+ ...(liveDesktop?.actorEvidence?.actorLogTailPath ? [{ label: "actor log tail", path: liveDesktop.actorEvidence.actorLogTailPath, kind: "log" }] : []),
1260
+ ...(liveDesktop?.actorEvidence?.setupQualityPath ? [{ label: "setup quality", path: liveDesktop.actorEvidence.setupQualityPath, kind: "filesystem" }] : []),
1261
+ ...(liveDesktop?.hostActorPlanPath ? [{ label: "host Codex actor plan", path: liveDesktop.hostActorPlanPath, kind: "trace" }] : []),
288
1262
  ...(liveDesktop?.bootstrap?.logPath ? [{ label: "remote bootstrap log", path: liveDesktop.bootstrap.logPath, kind: "log" }] : []),
289
1263
  ...(liveDesktop?.bootstrap?.nestedObserverPath ? [{ label: "nested observer path", path: liveDesktop.bootstrap.nestedObserverPath, kind: "observer" }] : []),
290
1264
  ...(screenshot ? [{ label: "desktop screenshot", path: screenshot.path, kind: "screenshot" }] : [])
@@ -295,7 +1269,7 @@ function buildMetaBundle(args) {
295
1269
  at: args.createdAt,
296
1270
  level: "info",
297
1271
  type: "oss-meta.repo.assigned",
298
- message: `Assigned ${assignment.repo} to Codex desktop lane ${assignment.index}.`,
1272
+ message: `Assigned ${repoLabel} to Codex desktop lane ${assignment.index}.`,
299
1273
  simId: assignment.simId,
300
1274
  streamId: assignment.streamId
301
1275
  }, {
@@ -307,13 +1281,13 @@ function buildMetaBundle(args) {
307
1281
  simId: assignment.simId,
308
1282
  streamId: assignment.streamId
309
1283
  });
310
- if (liveDesktop?.url) {
1284
+ if (liveStreamPresent && liveDesktop) {
311
1285
  events.push({
312
1286
  id: `event-${String(assignment.index).padStart(3, "0")}-stream`,
313
1287
  at: args.createdAt,
314
1288
  level: "info",
315
1289
  type: "oss-meta.e2b.stream.started",
316
- message: `Live E2B desktop stream started for ${assignment.repo}.`,
1290
+ message: `Live E2B desktop stream started for ${repoLabel}; auth URL is runtime-only and not persisted in run artifacts.`,
317
1291
  simId: assignment.simId,
318
1292
  streamId: assignment.streamId
319
1293
  });
@@ -323,7 +1297,7 @@ function buildMetaBundle(args) {
323
1297
  at: args.createdAt,
324
1298
  level: "info",
325
1299
  type: "oss-meta.bootstrap.started",
326
- message: `Visible bootstrap terminal launched for ${assignment.repo}.`,
1300
+ message: `Visible bootstrap terminal launched for ${repoLabel}.`,
327
1301
  simId: assignment.simId,
328
1302
  streamId: assignment.streamId
329
1303
  });
@@ -333,7 +1307,7 @@ function buildMetaBundle(args) {
333
1307
  at: completion.checkedAt,
334
1308
  level: eventLevelForCompletion(completion.status),
335
1309
  type: `oss-meta.bootstrap.${completion.status}`,
336
- message: `${assignment.repo}: ${completion.reason}`,
1310
+ message: `${repoLabel}: ${completion.reason}`,
337
1311
  simId: assignment.simId,
338
1312
  streamId: assignment.streamId
339
1313
  });
@@ -345,19 +1319,30 @@ function buildMetaBundle(args) {
345
1319
  at: args.createdAt,
346
1320
  level: "error",
347
1321
  type: "oss-meta.bootstrap.failed",
348
- message: `Bootstrap launcher failed for ${assignment.repo}.`,
1322
+ message: `Bootstrap launcher failed for ${repoLabel}.`,
349
1323
  simId: assignment.simId,
350
1324
  streamId: assignment.streamId
351
1325
  });
352
1326
  }
353
1327
  }
1328
+ else if (completion) {
1329
+ events.push({
1330
+ id: `event-${String(assignment.index).padStart(3, "0")}-completion-${completion.status}`,
1331
+ at: completion.checkedAt,
1332
+ level: eventLevelForCompletion(completion.status),
1333
+ type: `oss-meta.bootstrap.${completion.status}`,
1334
+ message: `${repoLabel}: ${completion.reason}`,
1335
+ simId: assignment.simId,
1336
+ streamId: assignment.streamId
1337
+ });
1338
+ }
354
1339
  else if (liveDesktop?.error) {
355
1340
  events.push({
356
1341
  id: `event-${String(assignment.index).padStart(3, "0")}-stream-error`,
357
1342
  at: args.createdAt,
358
1343
  level: "error",
359
1344
  type: "oss-meta.e2b.stream.failed",
360
- message: `E2B desktop stream failed for ${assignment.repo}: ${liveDesktop.error}`,
1345
+ message: `E2B desktop stream failed for ${repoLabel}: ${liveDesktop.error}`,
361
1346
  simId: assignment.simId,
362
1347
  streamId: assignment.streamId
363
1348
  });
@@ -395,10 +1380,16 @@ function buildMetaBundle(args) {
395
1380
  });
396
1381
  }
397
1382
  const review = createMetaReview(args);
1383
+ const feedbackCandidates = buildMetaFeedbackCandidates({
1384
+ assignments: args.assignments,
1385
+ liveDesktops: args.liveDesktops,
1386
+ redactRepoNames: args.redactRepoNames,
1387
+ runId: args.runId
1388
+ });
398
1389
  return {
399
1390
  schema: RUN_BUNDLE_SCHEMA,
400
1391
  runId: args.runId,
401
- mode: "dry-run",
1392
+ mode: args.dryRun ? "dry-run" : "live",
402
1393
  simCount: args.assignments.length,
403
1394
  createdAt: args.createdAt,
404
1395
  cwd: args.cwd,
@@ -420,7 +1411,7 @@ function buildMetaBundle(args) {
420
1411
  scenario: {
421
1412
  id: "oss-meta-observer-of-observers",
422
1413
  title: "OSS Observer-of-Observers Meta-Lab",
423
- goal: "Launch headed E2B desktops where Codex agents clone public OSS repos, set up Mimetic, run nested Mimetic proof commands, attempt Codex TUI, and keep each nested Observer visible.",
1414
+ goal: "Launch headed E2B desktops where Codex agents clone authorized GitHub repos, set up Mimetic, run nested Mimetic proof commands, attempt Codex TUI, and keep each nested Observer visible.",
424
1415
  source: "lab:oss:meta",
425
1416
  sourceDigest: "public-safe"
426
1417
  },
@@ -433,7 +1424,7 @@ function buildMetaBundle(args) {
433
1424
  {
434
1425
  at: args.createdAt,
435
1426
  event: "oss-meta.repos.assigned",
436
- message: `Assigned repos: ${args.assignments.map((assignment) => assignment.repo).join(", ")}.`
1427
+ message: `Assigned repos: ${args.assignments.map((assignment) => args.redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo).join(", ")}.`
437
1428
  },
438
1429
  {
439
1430
  at: args.createdAt,
@@ -446,7 +1437,7 @@ function buildMetaBundle(args) {
446
1437
  events,
447
1438
  redaction: {
448
1439
  status: "passed",
449
- notes: "OSS meta-lab artifacts contain public GitHub slugs and synthetic bootstrap prompts only."
1440
+ notes: "OSS meta-lab artifacts contain GitHub slugs and redacted/synthetic bootstrap evidence only."
450
1441
  },
451
1442
  artifacts: {
452
1443
  run: "run.json",
@@ -456,12 +1447,16 @@ function buildMetaBundle(args) {
456
1447
  events: "events.ndjson"
457
1448
  },
458
1449
  review,
459
- feedbackCandidates: []
1450
+ feedbackCandidates
460
1451
  };
461
1452
  }
462
1453
  function statusForMeta(args, liveDesktop) {
463
1454
  if (args.dryRun)
464
1455
  return "contract_proof_only";
1456
+ if (liveDesktop?.completion?.status === "passed" && liveDesktop.completion.appStatus !== "running")
1457
+ return "blocked";
1458
+ if (liveDesktop?.completion?.status === "passed" && liveDesktop.completion.visualStatus !== "visible")
1459
+ return "blocked";
465
1460
  if (liveDesktop?.completion?.status === "passed")
466
1461
  return "passed";
467
1462
  if (liveDesktop?.completion?.status === "failed")
@@ -493,49 +1488,64 @@ function progressForMeta(status, liveDesktop) {
493
1488
  return 18;
494
1489
  if (status === "failed")
495
1490
  return 8;
1491
+ if (liveDesktop?.completion?.appStatus === "running")
1492
+ return 86;
496
1493
  if (liveDesktop?.bootstrap?.status === "started")
497
1494
  return 74;
498
1495
  if (liveDesktop?.url)
499
1496
  return 58;
500
1497
  return 34;
501
1498
  }
502
- function currentStepForMeta(args, assignment) {
503
- const liveDesktop = args.liveDesktops.find((desktop) => desktop.streamId === assignment.streamId);
1499
+ function currentStepForMeta(args, assignment, liveDesktop) {
1500
+ const repoLabel = args.redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo;
504
1501
  if (args.dryRun) {
505
- return `Contract ready for ${assignment.repo}; no E2B desktop launched.`;
1502
+ return `Contract ready for ${repoLabel}; no E2B desktop launched.`;
506
1503
  }
507
1504
  if (liveDesktop?.completion) {
508
1505
  return liveDesktop.completion.reason;
509
1506
  }
510
1507
  if (liveDesktop?.bootstrap?.status === "started") {
511
- return `Bootstrap terminal launched for ${assignment.repo}; Codex TUI attempt, Mimetic setup, and nested Observer run inside the desktop.`;
1508
+ return `Bootstrap terminal launched for ${repoLabel}; Codex TUI attempt, Mimetic setup, and nested Observer run inside the desktop.`;
512
1509
  }
513
1510
  if (liveDesktop?.bootstrap?.status === "failed") {
514
- return `Bootstrap launcher failed for ${assignment.repo}.`;
1511
+ return `Bootstrap launcher failed for ${repoLabel}.`;
515
1512
  }
516
1513
  if (liveDesktop?.url) {
517
- return `Live E2B desktop connected for ${assignment.repo}; Codex TUI injection pending.`;
1514
+ return `Live E2B desktop connected for ${repoLabel}; Codex TUI injection pending.`;
518
1515
  }
519
1516
  if (liveDesktop?.error) {
520
- return `E2B desktop launch failed for ${assignment.repo}.`;
1517
+ return `E2B desktop launch failed for ${repoLabel}.`;
521
1518
  }
522
1519
  if (args.missingKeys.length > 0) {
523
- return `Waiting for ${args.missingKeys.join(", ")} before launching ${assignment.repo}.`;
1520
+ return `Waiting for ${args.missingKeys.join(", ")} before launching ${repoLabel}.`;
524
1521
  }
525
- return `Ready to launch E2B desktop and inject Codex TUI for ${assignment.repo}.`;
1522
+ return `Ready to launch E2B desktop and inject Codex TUI for ${repoLabel}.`;
526
1523
  }
527
1524
  function createMetaReview(args) {
528
1525
  const started = args.liveDesktops.filter((desktop) => desktop.bootstrap?.status === "started");
529
1526
  const terminalCompletions = args.liveDesktops.filter((desktop) => isTerminalCompletion(desktop.completion));
1527
+ const appRunning = args.liveDesktops.filter((desktop) => desktop.completion?.appStatus === "running");
1528
+ const visualVisible = args.liveDesktops.filter((desktop) => desktop.completion?.visualStatus === "visible");
1529
+ const nestedLiveProof = args.liveDesktops.some((desktop) => desktop.completion?.nestedVerifyPassed === true
1530
+ && /\bmimetic run live\b/.test(desktop.completion.logTail ?? ""));
530
1531
  const outcome = classifyMetaLabOutcome(args);
531
1532
  const gaps = [
532
- started.length > 0 && terminalCompletions.length === started.length
533
- ? "OSS lane terminal states are classified from public-safe remote bootstrap evidence; this still proves nested Mimetic setup rather than target product behavior."
534
- : args.liveDesktops.some((desktop) => desktop.bootstrap?.status === "started")
535
- ? "Visible E2B bootstrap terminals are launched and run nested Mimetic setup; completion is watched in the desktop stream rather than polled back into the top-level bundle yet."
536
- : "Nested Mimetic Observer evidence is represented as a lane contract until Codex TUI injection and nested Mimetic execution land.",
1533
+ nestedLiveProof && appRunning.length > 0 && visualVisible.length > 0
1534
+ ? "Target app browser surfaces, nested Observer windows, and nested Mimetic live app-url proof are visible inside headed desktops."
1535
+ : appRunning.length > 0 && visualVisible.length > 0
1536
+ ? "Target app browser surfaces and nested Observer windows are visible inside headed desktops; real Mimetic browser personas driving those apps are still the next adapter slice."
1537
+ : appRunning.length > 0
1538
+ ? "Target app surfaces responded over HTTP, but headed desktop browser-window visibility was not detected for every lane."
1539
+ : started.length > 0 && terminalCompletions.length === started.length
1540
+ ? "OSS lane terminal states are classified from public-safe remote bootstrap evidence, but target app HTTP readiness was not detected."
1541
+ : args.liveDesktops.some((desktop) => desktop.bootstrap?.status === "started")
1542
+ ? "Visible E2B bootstrap terminals are launched and run nested Mimetic setup plus target app startup; completion is watched in the desktop stream until remote evidence is polled back."
1543
+ : "Nested Mimetic Observer evidence is represented as a lane contract until Codex TUI injection and nested Mimetic execution land.",
1544
+ nestedLiveProof
1545
+ ? "Nested Mimetic proof reached live app-url mode with desktop/mobile browser evidence; autonomous multi-step persona navigation is still the next adapter slice."
1546
+ : "Nested Mimetic proof did not reach live app-url mode; target app startup or browser evidence is still missing.",
537
1547
  "The top-level run does not clone, modify, commit, push, or file issues in target repos.",
538
- "Only public GitHub owner/repo slugs are recorded."
1548
+ "Public runs may record GitHub owner/repo slugs; token-backed maintainer/private runs redact repo labels in durable artifacts by default."
539
1549
  ];
540
1550
  if (args.liveRequested && args.missingKeys.length > 0) {
541
1551
  gaps.unshift(`Live launch is blocked until ${args.missingKeys.join(", ")} are available in environment.`);
@@ -551,9 +1561,9 @@ function createMetaReview(args) {
551
1561
  : args.dryRun
552
1562
  ? "OSS meta-lab dry-run rendered the Observer-of-Observers contract without provider spend."
553
1563
  : terminalCompletions.length > 0
554
- ? `OSS meta-lab launched live E2B desktop streams and classified ${terminalCompletions.length}/${started.length || terminalCompletions.length} bootstrap terminal state${terminalCompletions.length === 1 ? "" : "s"} from public-safe remote evidence.`
1564
+ ? `OSS meta-lab launched live E2B desktop streams, classified ${terminalCompletions.length}/${started.length || terminalCompletions.length} bootstrap terminal state${terminalCompletions.length === 1 ? "" : "s"} from public-safe remote evidence, detected ${appRunning.length}/${terminalCompletions.length} target app HTTP-ready surface${terminalCompletions.length === 1 ? "" : "s"}, and detected ${visualVisible.length}/${terminalCompletions.length} headed desktop visual layout${terminalCompletions.length === 1 ? "" : "s"}.`
555
1565
  : args.liveDesktops.some((desktop) => desktop.bootstrap?.status === "started")
556
- ? "OSS meta-lab launched live E2B desktop streams, injected visible bootstrap terminals, and started nested Mimetic setup inside each desktop."
1566
+ ? "OSS meta-lab launched live E2B desktop streams, injected visible bootstrap terminals, and started target app plus nested Mimetic setup inside each desktop."
557
1567
  : args.liveDesktops.some((desktop) => desktop.url)
558
1568
  ? "OSS meta-lab launched live E2B desktop streams and rendered them in the top-level Observer."
559
1569
  : "OSS meta-lab rendered the live headed-desktop control surface and marked the missing substrate truth in-lane.",
@@ -607,10 +1617,28 @@ function classifyMetaLabOutcome(args) {
607
1617
  verdict: "blocked"
608
1618
  };
609
1619
  }
1620
+ const completedWithMissingApp = args.liveDesktops.filter((desktop) => desktop.completion?.status === "passed"
1621
+ && desktop.completion.appStatus !== "running");
1622
+ if (completedWithMissingApp.length > 0) {
1623
+ return {
1624
+ ok: false,
1625
+ reason: `OSS meta-lab completed nested Mimetic setup but did not detect ${completedWithMissingApp.length}/${args.liveDesktops.length} target app HTTP-ready surface${completedWithMissingApp.length === 1 ? "" : "s"}.`,
1626
+ verdict: "blocked"
1627
+ };
1628
+ }
1629
+ const completedWithMissingVisual = args.liveDesktops.filter((desktop) => desktop.completion?.status === "passed"
1630
+ && desktop.completion.visualStatus !== "visible");
1631
+ if (completedWithMissingVisual.length > 0) {
1632
+ return {
1633
+ ok: false,
1634
+ reason: `OSS meta-lab completed nested Mimetic setup but did not detect ${completedWithMissingVisual.length}/${args.liveDesktops.length} headed desktop visual layout${completedWithMissingVisual.length === 1 ? "" : "s"}.`,
1635
+ verdict: "blocked"
1636
+ };
1637
+ }
610
1638
  if (args.liveDesktops.length > 0 && args.liveDesktops.every((desktop) => desktop.completion?.status === "passed")) {
611
1639
  return {
612
1640
  ok: true,
613
- reason: `OSS meta-lab passed ${args.liveDesktops.length}/${args.liveDesktops.length} bootstrap terminal states from public-safe remote evidence.`,
1641
+ reason: `OSS meta-lab passed ${args.liveDesktops.length}/${args.liveDesktops.length} bootstrap terminal states with target app HTTP readiness and headed desktop visual layout detected from public-safe remote evidence.`,
614
1642
  verdict: "pass"
615
1643
  };
616
1644
  }
@@ -627,26 +1655,209 @@ function terminalTailForMeta(prompt, liveDesktop) {
627
1655
  const lines = [
628
1656
  `Remote bootstrap ${liveDesktop.completion.status}: ${liveDesktop.completion.reason}`,
629
1657
  `checked_at: ${liveDesktop.completion.checkedAt}`,
1658
+ ...(liveDesktop.completion.appStatus === undefined ? [] : [`app_status: ${liveDesktop.completion.appStatus}`]),
1659
+ ...(liveDesktop.completion.appUrl === undefined ? [] : [`app_url: ${liveDesktop.completion.appUrl}`]),
1660
+ ...(liveDesktop.completion.appReason === undefined ? [] : [`app_reason: ${liveDesktop.completion.appReason}`]),
1661
+ ...(liveDesktop.completion.actorStatus === undefined ? [] : [`actor_status: ${liveDesktop.completion.actorStatus}`]),
630
1662
  ...(liveDesktop.completion.exitCode === undefined ? [] : [`exit_code: ${liveDesktop.completion.exitCode}`]),
631
1663
  ...(liveDesktop.completion.nestedVerifyPassed === undefined ? [] : [`nested_verify_passed: ${liveDesktop.completion.nestedVerifyPassed ? "true" : "false"}`]),
632
1664
  ...(liveDesktop.completion.nestedObserverPresent === undefined ? [] : [`nested_observer_present: ${liveDesktop.completion.nestedObserverPresent ? "true" : "false"}`]),
1665
+ ...(liveDesktop.completion.visualStatus === undefined ? [] : [`visual_status: ${liveDesktop.completion.visualStatus}`]),
1666
+ ...(liveDesktop.completion.visualWindowCount === undefined ? [] : [`visual_window_count: ${liveDesktop.completion.visualWindowCount}`]),
1667
+ ...(liveDesktop.completion.visualReason === undefined ? [] : [`visual_reason: ${liveDesktop.completion.visualReason}`]),
1668
+ ...(liveDesktop.completion.setupQuality === undefined ? [] : [
1669
+ `setup_quality: ${liveDesktop.completion.setupQuality.status}`,
1670
+ `setup_summary: ${liveDesktop.completion.setupQuality.summary}`,
1671
+ ...(liveDesktop.completion.setupQuality.studyQuality === undefined ? [] : [
1672
+ `study_quality: ${liveDesktop.completion.setupQuality.studyQuality.rating}`,
1673
+ `study_summary: ${liveDesktop.completion.setupQuality.studyQuality.summary}`
1674
+ ])
1675
+ ]),
1676
+ "",
1677
+ "public-safe actor last message tail:",
1678
+ liveDesktop.completion.actorLastMessageTail?.trim() || "(no actor last-message captured)",
1679
+ "",
1680
+ "public-safe actor log tail:",
1681
+ liveDesktop.completion.actorLogTail?.trim() || "(no actor log tail captured)",
633
1682
  "",
634
- "public-safe log tail:",
1683
+ "public-safe bootstrap log tail:",
635
1684
  liveDesktop.completion.logTail?.trim() || "(no log tail captured)"
636
1685
  ];
637
1686
  return lines.join("\n").trim();
638
1687
  }
639
1688
  function completionForStream(completion) {
640
1689
  return {
1690
+ ...(completion.actorLogPath === undefined ? {} : { actorLogPath: completion.actorLogPath }),
1691
+ ...(completion.actorLogTail === undefined ? {} : { actorLogTail: completion.actorLogTail }),
1692
+ ...(completion.actorLastMessageTail === undefined ? {} : { actorLastMessageTail: completion.actorLastMessageTail }),
1693
+ ...(completion.actorPid === undefined ? {} : { actorPid: completion.actorPid }),
1694
+ ...(completion.actorStatus === undefined ? {} : { actorStatus: completion.actorStatus }),
1695
+ ...(completion.appLogPath === undefined ? {} : { appLogPath: completion.appLogPath }),
1696
+ ...(completion.appPid === undefined ? {} : { appPid: completion.appPid }),
1697
+ ...(completion.appReason === undefined ? {} : { appReason: completion.appReason }),
1698
+ ...(completion.appStatus === undefined ? {} : { appStatus: completion.appStatus }),
1699
+ ...(completion.appUrl === undefined ? {} : { appUrl: completion.appUrl }),
641
1700
  checkedAt: completion.checkedAt,
642
1701
  ...(completion.exitCode === undefined ? {} : { exitCode: completion.exitCode }),
643
1702
  ...(completion.logTail === undefined ? {} : { logTail: completion.logTail }),
644
1703
  ...(completion.nestedObserverPresent === undefined ? {} : { nestedObserverPresent: completion.nestedObserverPresent }),
645
1704
  ...(completion.nestedVerifyPassed === undefined ? {} : { nestedVerifyPassed: completion.nestedVerifyPassed }),
646
1705
  reason: completion.reason,
647
- status: completion.status
1706
+ status: completion.status,
1707
+ ...(completion.visualReason === undefined ? {} : { visualReason: completion.visualReason }),
1708
+ ...(completion.visualStatus === undefined ? {} : { visualStatus: completion.visualStatus }),
1709
+ ...(completion.visualWindowCount === undefined ? {} : { visualWindowCount: completion.visualWindowCount })
648
1710
  };
649
1711
  }
1712
+ function buildMetaFeedbackCandidates(args) {
1713
+ const candidates = [];
1714
+ for (const assignment of args.assignments) {
1715
+ const desktop = args.liveDesktops.find((lane) => lane.streamId === assignment.streamId);
1716
+ if (!desktop) {
1717
+ continue;
1718
+ }
1719
+ const repoLabel = args.redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo;
1720
+ const scenarioId = args.redactRepoNames ? `oss-meta-${repoLabel}` : assignment.scenarioId;
1721
+ const baseEvidence = feedbackEvidenceForDesktop(desktop);
1722
+ const setupQualityPath = desktop.actorEvidence?.setupQualityPath;
1723
+ const failedSetupChecks = desktop.completion?.setupQuality?.checks.filter((check) => !check.ok) ?? [];
1724
+ const studyQuality = desktop.completion?.setupQuality?.studyQuality;
1725
+ if (setupQualityPath && failedSetupChecks.length > 0) {
1726
+ candidates.push({
1727
+ schema: "mimetic.feedback-candidate.v1",
1728
+ id: `setup-quality-${safeArtifactToken(assignment.streamId)}`,
1729
+ run_id: args.runId,
1730
+ stream_id: assignment.streamId,
1731
+ adapter_id: "oss-meta-lab",
1732
+ scenario_id: scenarioId,
1733
+ persona_id: `codex-oss-operator-${String(assignment.index).padStart(2, "0")}`,
1734
+ actor: "codex-tui",
1735
+ substrate: "e2b-desktop",
1736
+ failure_owner: "actor",
1737
+ summary: `${repoLabel} Mimetic setup needs review`,
1738
+ expected: "The setup actor should create committed Mimetic source files, useful personas/scenarios, a package script, and a .mimetic/ runtime ignore without preserving private state.",
1739
+ actual: failedSetupChecks.map((check) => `${check.label}: ${check.detail}`).join(" "),
1740
+ evidence: [
1741
+ {
1742
+ path: setupQualityPath,
1743
+ kind: "filesystem",
1744
+ note: "Setup-quality snapshot with tree, checks, package scripts, and allowlisted previews."
1745
+ },
1746
+ ...baseEvidence
1747
+ ],
1748
+ redaction: {
1749
+ status: "passed",
1750
+ notes: "Feedback candidate references local public-safe run artifacts only."
1751
+ },
1752
+ idempotency_key: `mimetic:${args.runId}:${assignment.streamId}:setup-quality`,
1753
+ proposed_next_state: "setup-quality-review",
1754
+ acceptance_proof: [
1755
+ `pnpm mimetic -- verify --run ${args.runId} --json`,
1756
+ `pnpm mimetic -- watch --run ${args.runId} --no-open`
1757
+ ]
1758
+ });
1759
+ }
1760
+ const actorText = `${desktop.completion?.actorLastMessageTail ?? ""}\n${desktop.completion?.actorLogTail ?? ""}`;
1761
+ if (hasAppUrlProofBlocker(actorText)) {
1762
+ candidates.push({
1763
+ schema: "mimetic.feedback-candidate.v1",
1764
+ id: `published-cli-app-url-${safeArtifactToken(assignment.streamId)}`,
1765
+ run_id: args.runId,
1766
+ stream_id: assignment.streamId,
1767
+ adapter_id: "oss-meta-lab",
1768
+ scenario_id: scenarioId,
1769
+ persona_id: `codex-oss-operator-${String(assignment.index).padStart(2, "0")}`,
1770
+ actor: "codex-tui",
1771
+ substrate: "e2b-desktop",
1772
+ failure_owner: "harness",
1773
+ summary: "Published Mimetic install path blocked app-url proof",
1774
+ expected: "A fresh npm-installed Mimetic CLI should support the app-url live proof path documented for agents.",
1775
+ actual: "The actor evidence reports that the installed CLI did not accept or expose the app-url proof option.",
1776
+ evidence: baseEvidence,
1777
+ redaction: {
1778
+ status: "passed",
1779
+ notes: "Actor evidence was redacted before persistence."
1780
+ },
1781
+ idempotency_key: `mimetic:${args.runId}:${assignment.streamId}:published-cli-app-url`,
1782
+ proposed_next_state: "adapter-hardening",
1783
+ acceptance_proof: [
1784
+ "npm view mimetic-cli version",
1785
+ "npx --package mimetic-cli mimetic run --help | grep -- --app-url",
1786
+ `pnpm mimetic -- verify --run ${args.runId} --json`
1787
+ ]
1788
+ });
1789
+ }
1790
+ if (setupQualityPath && studyQuality && (studyQuality.rating === "none" || studyQuality.rating === "ceremonial")) {
1791
+ candidates.push({
1792
+ schema: "mimetic.feedback-candidate.v1",
1793
+ id: `study-quality-${safeArtifactToken(assignment.streamId)}`,
1794
+ run_id: args.runId,
1795
+ stream_id: assignment.streamId,
1796
+ adapter_id: "oss-meta-lab",
1797
+ scenario_id: scenarioId,
1798
+ persona_id: `codex-oss-operator-${String(assignment.index).padStart(2, "0")}`,
1799
+ actor: "codex-tui",
1800
+ substrate: "e2b-desktop",
1801
+ failure_owner: "actor",
1802
+ summary: `${repoLabel} Mimetic setup was ${studyQuality.rating}`,
1803
+ expected: "The setup actor should turn Mimetic init into an app-aware user-study plan with customized coverage, personas, scenarios, app-url proof, and public-safe feedback.",
1804
+ actual: studyQuality.summary,
1805
+ evidence: [
1806
+ {
1807
+ path: setupQualityPath,
1808
+ kind: "filesystem",
1809
+ note: "Setup-quality snapshot includes study-quality checks and public-safe structural signals."
1810
+ },
1811
+ ...baseEvidence
1812
+ ],
1813
+ redaction: {
1814
+ status: "passed",
1815
+ notes: "Study-quality feedback candidate references local public-safe artifacts only."
1816
+ },
1817
+ idempotency_key: `mimetic:${args.runId}:${assignment.streamId}:study-quality`,
1818
+ proposed_next_state: "study-quality-review",
1819
+ acceptance_proof: [
1820
+ `pnpm mimetic -- verify --run ${args.runId} --json`,
1821
+ `pnpm mimetic -- watch --run ${args.runId} --no-open`,
1822
+ "Study-quality rating is useful or high_leverage, or the remaining ceremonial state is explicitly explained."
1823
+ ]
1824
+ });
1825
+ }
1826
+ }
1827
+ return candidates.slice(0, 20);
1828
+ }
1829
+ function feedbackEvidenceForDesktop(desktop) {
1830
+ const evidence = [];
1831
+ if (desktop.actorEvidence?.actorLastMessageTailPath) {
1832
+ evidence.push({
1833
+ path: desktop.actorEvidence.actorLastMessageTailPath,
1834
+ kind: "log",
1835
+ note: "Public-safe actor last-message tail."
1836
+ });
1837
+ }
1838
+ if (desktop.actorEvidence?.actorLogTailPath) {
1839
+ evidence.push({
1840
+ path: desktop.actorEvidence.actorLogTailPath,
1841
+ kind: "log",
1842
+ note: "Public-safe actor log tail."
1843
+ });
1844
+ }
1845
+ if (desktop.screenshot?.path) {
1846
+ evidence.push({
1847
+ path: desktop.screenshot.path,
1848
+ kind: "screenshot",
1849
+ note: "Headed desktop screenshot fallback."
1850
+ });
1851
+ }
1852
+ if (desktop.hostActorPlanPath) {
1853
+ evidence.push({
1854
+ path: desktop.hostActorPlanPath,
1855
+ kind: "trace",
1856
+ note: "Host-authored public-safe actor plan."
1857
+ });
1858
+ }
1859
+ return evidence;
1860
+ }
650
1861
  function eventLevelForCompletion(status) {
651
1862
  if (status === "passed")
652
1863
  return "info";
@@ -659,25 +1870,30 @@ function eventLevelForCompletion(status) {
659
1870
  function isTerminalCompletion(completion) {
660
1871
  return completion !== undefined && completion.status !== "running";
661
1872
  }
662
- function buildCodexBootstrapPrompt(assignment) {
1873
+ function buildCodexBootstrapPrompt(assignment, redactRepoName = false) {
1874
+ const repoLabel = redactRepoName ? "[redacted-authorized-repo]" : `https://github.com/${assignment.repo}.git`;
663
1875
  return [
664
1876
  `# Mimetic OSS Meta-Lab Actor ${assignment.index}`,
665
1877
  "",
666
1878
  "You are running inside a disposable headed E2B desktop with a visible terminal and browser.",
667
- "Public-safety hard rails: use only public repo contents; never print keys; never commit, push, file issues, or preserve private artifacts.",
1879
+ "Public-safety hard rails: use only authorized repo contents; never print keys; never commit, push, file issues, or preserve private artifacts.",
668
1880
  "",
669
- `Target repo: https://github.com/${assignment.repo}.git`,
1881
+ `Target repo: ${repoLabel}`,
670
1882
  "",
671
1883
  "Mission:",
672
1884
  "1. Clone the target repo into a clean disposable workspace.",
673
1885
  "2. Inspect the package manager, dev scripts, README, and app shape.",
674
1886
  "3. Get the repo into a local runnable dev mode if feasible.",
675
- "4. Install Mimetic as a dev dependency with the package manager the repo already uses.",
676
- "5. Run `npx mimetic init --yes` or the package-manager equivalent.",
677
- "6. Author plausible public-safe Mimetic personas and scenarios for this repo.",
678
- "7. Run the strongest Mimetic proof path the installed package supports, using provided OPENAI_API_KEY and E2B_API_KEY where live substrate is implemented.",
679
- "8. Open the nested Mimetic Observer in the E2B browser and keep it visible.",
680
- "9. Record public-safe blockers and evidence paths only.",
1887
+ "4. Discover the Mimetic skill path and try installing it with `npx skills add danielgwilson/mimetic-cli --skill mimetic-cli`.",
1888
+ "5. Install Mimetic as a dev dependency with the package manager the repo already uses.",
1889
+ "6. Run `npx mimetic init --yes` or the package-manager equivalent.",
1890
+ "7. Replace starter coverage files with an app-aware `mimetic/coverage-map.md` and `mimetic/coverage-matrix.md` that name real screens, roles, states, happy paths, and at least one sad/friction path discovered from the repo/app.",
1891
+ "8. Author at least two public-safe, app-specific Mimetic personas and two desktop/mobile browser scenarios. Avoid generic `first-run-smoke` only; each scenario should name a target journey, route/state, expected evidence, and a failure/friction check.",
1892
+ "9. Run `npx mimetic run --help` and verify `--app-url` is available.",
1893
+ "10. If the app is running locally, run `npx mimetic run --app-url <loopback-url> --sims 2`; do not use `mimetic watch --sims` as app behavior proof.",
1894
+ "11. After `run --app-url`, render or open the nested Mimetic Observer with `npx mimetic watch --run latest --detach --no-open --json` and keep it visible.",
1895
+ "12. Final summary must be public-safe and include: personas/scenarios created, product journeys covered, one observed friction/improvement or `none observed`, and evidence paths. Do not stop at install/init proof.",
1896
+ "13. Record public-safe blockers and evidence paths only.",
681
1897
  "",
682
1898
  "Expected nested outcome: the top-level Mimetic Observer shows this desktop, and this desktop shows its own nested Mimetic Observer."
683
1899
  ].join("\n");
@@ -707,28 +1923,28 @@ ${bundle.review.gaps.map((gap) => `- ${gap}`).join("\n")}
707
1923
  }
708
1924
  async function launchLiveDesktops(assignments, options = {}) {
709
1925
  const e2bApiKey = process.env.E2B_API_KEY;
710
- const openaiApiKey = process.env.OPENAI_API_KEY;
711
- if (!e2bApiKey || !openaiApiKey) {
1926
+ if (!e2bApiKey) {
712
1927
  return [];
713
1928
  }
714
1929
  const desktopModule = await loadE2BDesktopModule();
715
1930
  const timeoutMs = readPositiveInt(process.env.MIMETIC_E2B_TIMEOUT_MS, 60 * 60 * 1000);
716
1931
  const requestTimeoutMs = readPositiveInt(process.env.MIMETIC_E2B_REQUEST_TIMEOUT_MS, 60_000);
717
1932
  return Promise.all(assignments.map(async (assignment) => {
1933
+ const repoLabel = options.redactRepoNames ? repoArtifactLabel(assignment) : assignment.repo;
1934
+ const hostActorPlanResult = options.hostActorPlansByStream?.get(assignment.streamId);
718
1935
  try {
719
1936
  const desktop = await desktopModule.Sandbox.create({
720
1937
  apiKey: e2bApiKey,
721
1938
  requestTimeoutMs,
722
1939
  timeoutMs,
723
1940
  metadata: {
724
- tool: "mimetic-cli",
725
- mode: "oss-meta-lab",
726
- repo: assignment.repo,
1941
+ ...OSS_META_LAB_PROVIDER_METADATA,
1942
+ repo: repoLabel,
727
1943
  simId: assignment.simId
728
1944
  },
729
1945
  envs: {
730
- E2B_API_KEY: e2bApiKey,
731
- OPENAI_API_KEY: openaiApiKey
1946
+ ...collectOssMetaLabRemoteEnv(process.env),
1947
+ ...collectOssMetaLabPrivateEnv(process.env)
732
1948
  },
733
1949
  resolution: [1440, 960],
734
1950
  dpi: 96,
@@ -736,7 +1952,11 @@ async function launchLiveDesktops(assignments, options = {}) {
736
1952
  onTimeout: "kill"
737
1953
  }
738
1954
  });
739
- const bootstrap = await startOssBootstrap(desktop, assignment, options.localPackage, requestTimeoutMs);
1955
+ const bootstrap = await startOssBootstrap(desktop, assignment, options.localPackage, requestTimeoutMs, {
1956
+ ...(hostActorPlanResult === undefined ? {} : { hostActorPlanResult }),
1957
+ repoLabel,
1958
+ token: options.redactRepoNames ? repoLabel : repoSlugToken(assignment.repo)
1959
+ });
740
1960
  await desktop.wait(750).catch(() => undefined);
741
1961
  await desktop.stream.start({ requireAuth: true });
742
1962
  const authKey = desktop.stream.getAuthKey();
@@ -749,7 +1969,9 @@ async function launchLiveDesktops(assignments, options = {}) {
749
1969
  return {
750
1970
  bootstrap,
751
1971
  desktop,
752
- repo: assignment.repo,
1972
+ ...(hostActorPlanResult?.plan ? { hostActorPlan: hostActorPlanResult.plan } : {}),
1973
+ ...(hostActorPlanResult?.artifactPath ? { hostActorPlanPath: hostActorPlanResult.artifactPath } : {}),
1974
+ repo: repoLabel,
753
1975
  sandboxId: desktop.sandboxId,
754
1976
  simId: assignment.simId,
755
1977
  streamId: assignment.streamId,
@@ -759,26 +1981,28 @@ async function launchLiveDesktops(assignments, options = {}) {
759
1981
  catch (error) {
760
1982
  return {
761
1983
  error: compactError(error),
762
- repo: assignment.repo,
1984
+ ...(hostActorPlanResult?.plan ? { hostActorPlan: hostActorPlanResult.plan } : {}),
1985
+ ...(hostActorPlanResult?.artifactPath ? { hostActorPlanPath: hostActorPlanResult.artifactPath } : {}),
1986
+ repo: repoLabel,
763
1987
  simId: assignment.simId,
764
1988
  streamId: assignment.streamId
765
1989
  };
766
1990
  }
767
1991
  }));
768
1992
  }
769
- async function pollLiveDesktopCompletions(liveDesktops) {
1993
+ async function pollLiveDesktopCompletions(liveDesktops, options = {}) {
770
1994
  const pollable = liveDesktops.filter((desktop) => desktop.desktop
771
1995
  && desktop.bootstrap?.status === "started"
772
1996
  && desktop.bootstrap.completionPath);
773
1997
  if (pollable.length === 0) {
774
1998
  return { warnings: [] };
775
1999
  }
776
- const timeoutMs = readNonNegativeInt(process.env.MIMETIC_OSS_META_COMPLETION_TIMEOUT_MS, 240_000);
2000
+ const timeoutMs = options.timeoutMs ?? readNonNegativeInt(process.env.MIMETIC_OSS_META_COMPLETION_TIMEOUT_MS, 240_000);
777
2001
  const intervalMs = readPositiveInt(process.env.MIMETIC_OSS_META_COMPLETION_INTERVAL_MS, 5_000);
778
2002
  const requestTimeoutMs = readPositiveInt(process.env.MIMETIC_E2B_REQUEST_TIMEOUT_MS, 60_000);
779
2003
  const warnings = [];
780
2004
  if (timeoutMs === 0) {
781
- warnings.push("OSS meta-lab completion polling skipped because MIMETIC_OSS_META_COMPLETION_TIMEOUT_MS=0.");
2005
+ warnings.push(`OSS meta-lab completion polling skipped because ${options.timeoutReason ?? "MIMETIC_OSS_META_COMPLETION_TIMEOUT_MS=0"}.`);
782
2006
  return { warnings };
783
2007
  }
784
2008
  const deadline = Date.now() + timeoutMs;
@@ -811,6 +2035,59 @@ async function pollLiveDesktopCompletions(liveDesktops) {
811
2035
  warnings.push(`Timed out waiting for ${pollable.filter((desktop) => desktop.completion?.status === "timed_out").length}/${pollable.length} OSS meta-lab bootstrap completion marker${pollable.length === 1 ? "" : "s"}.`);
812
2036
  return { warnings };
813
2037
  }
2038
+ async function refreshOssMetaLabLiveRuntime(runtime, options) {
2039
+ await refreshLiveDesktopProgress(runtime.liveDesktops, {
2040
+ timedOut: options.timedOut,
2041
+ timeoutMs: options.timeoutMs
2042
+ });
2043
+ if (options.captureScreenshots) {
2044
+ await captureLiveDesktopScreenshots(runtime.artifactRoot, runtime.liveDesktops);
2045
+ }
2046
+ await writeActorEvidenceArtifacts(runtime.artifactRoot, runtime.liveDesktops, {
2047
+ assignments: runtime.assignments,
2048
+ redactRepoNames: runtime.redactRepoNames
2049
+ });
2050
+ const bundle = buildMetaBundle({
2051
+ assignments: runtime.assignments,
2052
+ createdAt: runtime.createdAt,
2053
+ cwd: runtime.cwd,
2054
+ dryRun: runtime.dryRun,
2055
+ liveDesktops: runtime.liveDesktops,
2056
+ liveRequested: runtime.liveRequested,
2057
+ missingKeys: runtime.missingKeys,
2058
+ redactRepoNames: runtime.redactRepoNames,
2059
+ runId: runtime.runId
2060
+ });
2061
+ await writeMetaBundleArtifacts(runtime.artifactRoot, bundle);
2062
+ await renderObserver(runtime.cwd, runtime.runId, { open: false });
2063
+ }
2064
+ async function refreshLiveDesktopProgress(liveDesktops, options) {
2065
+ const requestTimeoutMs = readPositiveInt(process.env.MIMETIC_E2B_REQUEST_TIMEOUT_MS, 60_000);
2066
+ await Promise.all(liveDesktops.map(async (desktop) => {
2067
+ if (!desktop.desktop || desktop.bootstrap?.status !== "started") {
2068
+ return;
2069
+ }
2070
+ if (isTerminalCompletion(desktop.completion)) {
2071
+ return;
2072
+ }
2073
+ const completion = await readRemoteCompletion(desktop.desktop, desktop.bootstrap, requestTimeoutMs);
2074
+ if (completion) {
2075
+ desktop.completion = completion;
2076
+ return;
2077
+ }
2078
+ const logTail = await readRemoteLogTail(desktop.desktop, desktop.bootstrap, requestTimeoutMs);
2079
+ desktop.completion = {
2080
+ checkedAt: new Date().toISOString(),
2081
+ logTail,
2082
+ reason: options.timedOut
2083
+ ? `Timed out waiting ${options.timeoutMs}ms for remote bootstrap completion marker.`
2084
+ : logTail
2085
+ ? "Remote bootstrap is running; latest public-safe log tail is available."
2086
+ : "Remote bootstrap is running; waiting for first public-safe log output.",
2087
+ status: options.timedOut ? "timed_out" : "running"
2088
+ };
2089
+ }));
2090
+ }
814
2091
  async function readRemoteCompletion(desktop, bootstrap, requestTimeoutMs) {
815
2092
  if (!bootstrap.completionPath) {
816
2093
  return null;
@@ -849,6 +2126,8 @@ async function captureLiveDesktopScreenshots(artifactRoot, liveDesktops) {
849
2126
  return;
850
2127
  }
851
2128
  try {
2129
+ await arrangeLiveDesktopForScreenshot(desktop.desktop, desktop.bootstrap?.terminalTitle, readPositiveInt(process.env.MIMETIC_E2B_REQUEST_TIMEOUT_MS, 60_000));
2130
+ await desktop.desktop.wait(readPositiveInt(process.env.MIMETIC_OSS_META_SCREENSHOT_SETTLE_MS, 2_500)).catch(() => undefined);
852
2131
  const bytes = await desktop.desktop.screenshot("bytes");
853
2132
  const fileName = `${safeArtifactToken(desktop.streamId)}.png`;
854
2133
  const screenshotPath = path.join(screenshotRoot, fileName);
@@ -862,12 +2141,176 @@ async function captureLiveDesktopScreenshots(artifactRoot, liveDesktops) {
862
2141
  catch (error) {
863
2142
  warnings.push(`Screenshot capture failed for ${desktop.repo}: ${compactError(error)}`);
864
2143
  }
865
- }));
866
- const capturedCount = liveDesktops.filter((desktop) => desktop.screenshot).length;
867
- if (capturedCount > 0) {
868
- warnings.push(`Captured ${capturedCount}/${candidates.length} E2B desktop screenshot fallback${candidates.length === 1 ? "" : "s"}.`);
2144
+ }));
2145
+ const capturedCount = liveDesktops.filter((desktop) => desktop.screenshot).length;
2146
+ if (capturedCount > 0) {
2147
+ warnings.push(`Captured ${capturedCount}/${candidates.length} E2B desktop screenshot fallback${candidates.length === 1 ? "" : "s"}.`);
2148
+ }
2149
+ return { warnings };
2150
+ }
2151
+ async function writeActorEvidenceArtifacts(artifactRoot, liveDesktops, options) {
2152
+ const candidates = liveDesktops.filter((desktop) => desktop.completion?.actorLastMessageTail || desktop.completion?.actorLogTail || desktop.completion?.setupQuality);
2153
+ if (candidates.length === 0) {
2154
+ return { warnings: [] };
2155
+ }
2156
+ const actorEvidenceRoot = path.join(artifactRoot, "actor-evidence");
2157
+ const setupQualityRoot = path.join(artifactRoot, "setup-quality");
2158
+ await mkdir(actorEvidenceRoot, { recursive: true });
2159
+ await mkdir(setupQualityRoot, { recursive: true });
2160
+ let written = 0;
2161
+ for (const desktop of candidates) {
2162
+ const assignment = options.assignments.find((candidate) => candidate.streamId === desktop.streamId);
2163
+ const repoForRedaction = options.redactRepoNames ? assignment?.repo : undefined;
2164
+ const baseName = safeArtifactToken(desktop.streamId);
2165
+ const actorEvidence = {};
2166
+ if (desktop.completion?.actorLastMessageTail) {
2167
+ const relativePath = path.join("actor-evidence", `${baseName}-actor-last-message-tail.txt`);
2168
+ await writeFile(path.join(artifactRoot, relativePath), renderPublicSafeActorEvidenceText("actor-last-message", desktop.streamId, desktop.completion.actorLastMessageTail, {
2169
+ providerRuntimeId: options.redactRepoNames ? desktop.sandboxId : undefined,
2170
+ repo: repoForRedaction
2171
+ }), "utf8");
2172
+ actorEvidence.actorLastMessageTailPath = relativePath;
2173
+ written += 1;
2174
+ }
2175
+ if (desktop.completion?.actorLogTail) {
2176
+ const relativePath = path.join("actor-evidence", `${baseName}-actor-log-tail.txt`);
2177
+ await writeFile(path.join(artifactRoot, relativePath), renderPublicSafeActorEvidenceText("actor-log", desktop.streamId, desktop.completion.actorLogTail, {
2178
+ providerRuntimeId: options.redactRepoNames ? desktop.sandboxId : undefined,
2179
+ repo: repoForRedaction
2180
+ }), "utf8");
2181
+ actorEvidence.actorLogTailPath = relativePath;
2182
+ written += 1;
2183
+ }
2184
+ if (desktop.completion?.setupQuality) {
2185
+ const relativePath = path.join("setup-quality", `${baseName}-setup-quality.json`);
2186
+ const snapshot = options.redactRepoNames
2187
+ ? redactSetupQualityRepoMentions(suppressSetupQualityPreviews(desktop.completion.setupQuality), repoForRedaction)
2188
+ : desktop.completion.setupQuality;
2189
+ await writeJson(path.join(artifactRoot, relativePath), snapshot);
2190
+ actorEvidence.setupQualityPath = relativePath;
2191
+ written += 1;
2192
+ }
2193
+ desktop.actorEvidence = actorEvidence;
2194
+ }
2195
+ return {
2196
+ warnings: [
2197
+ `Persisted ${written} public-safe local actor evidence artifact${written === 1 ? "" : "s"}.`
2198
+ ]
2199
+ };
2200
+ }
2201
+ function suppressSetupQualityPreviews(snapshot) {
2202
+ return {
2203
+ ...snapshot,
2204
+ summary: sanitizeSetupQualityText(snapshot.summary),
2205
+ checks: snapshot.checks.map((check) => ({
2206
+ ...check,
2207
+ label: sanitizeSetupQualityText(check.label),
2208
+ detail: sanitizeSetupQualityText(check.detail)
2209
+ })),
2210
+ ...(snapshot.studyQuality ? { studyQuality: sanitizeStudyQualitySnapshot(snapshot.studyQuality) } : {}),
2211
+ previews: [],
2212
+ redaction: {
2213
+ status: "passed",
2214
+ rawPreviews: "suppressed",
2215
+ notes: "Raw file previews are suppressed for token-backed/private OSS meta-lab runs."
2216
+ }
2217
+ };
2218
+ }
2219
+ function renderPublicSafeActorEvidenceText(kind, streamId, text, redaction) {
2220
+ const sanitized = sanitizeRemoteLog(redactPrivateRuntimeMentions(text, redaction));
2221
+ return [
2222
+ `schema: mimetic.oss-meta-actor-evidence.v1`,
2223
+ `kind: ${kind}`,
2224
+ `stream: ${streamId}`,
2225
+ `redaction: passed`,
2226
+ "",
2227
+ sanitized || "(no actor evidence captured)"
2228
+ ].join("\n").trimEnd() + "\n";
2229
+ }
2230
+ function redactLiveDesktopRepoMentions(desktop, repo) {
2231
+ return {
2232
+ ...desktop,
2233
+ ...(desktop.bootstrap
2234
+ ? {
2235
+ bootstrap: {
2236
+ ...desktop.bootstrap,
2237
+ tail: redactPrivateRuntimeMentions(desktop.bootstrap.tail, { providerRuntimeId: desktop.sandboxId, repo })
2238
+ }
2239
+ }
2240
+ : {}),
2241
+ ...(desktop.completion
2242
+ ? { completion: redactCompletionRepoMentions(desktop.completion, repo, desktop.sandboxId) }
2243
+ : {})
2244
+ };
2245
+ }
2246
+ function redactCompletionRepoMentions(completion, repo, providerRuntimeId) {
2247
+ const redaction = { providerRuntimeId, repo };
2248
+ return {
2249
+ ...completion,
2250
+ ...(completion.actorLogTail === undefined ? {} : { actorLogTail: redactPrivateRuntimeMentions(completion.actorLogTail, redaction) }),
2251
+ ...(completion.actorLastMessageTail === undefined ? {} : { actorLastMessageTail: redactPrivateRuntimeMentions(completion.actorLastMessageTail, redaction) }),
2252
+ ...(completion.appReason === undefined ? {} : { appReason: redactPrivateRuntimeMentions(completion.appReason, redaction) }),
2253
+ ...(completion.logTail === undefined ? {} : { logTail: redactPrivateRuntimeMentions(completion.logTail, redaction) }),
2254
+ reason: redactPrivateRuntimeMentions(completion.reason, redaction),
2255
+ ...(completion.setupQuality === undefined ? {} : { setupQuality: redactSetupQualityRepoMentions(completion.setupQuality, repo) }),
2256
+ ...(completion.visualReason === undefined ? {} : { visualReason: redactPrivateRuntimeMentions(completion.visualReason, redaction) })
2257
+ };
2258
+ }
2259
+ function redactSetupQualityRepoMentions(snapshot, repo) {
2260
+ if (!repo) {
2261
+ return snapshot;
869
2262
  }
870
- return { warnings };
2263
+ return JSON.parse(JSON.stringify(snapshot, (_key, value) => typeof value === "string" ? redactRepoMentions(value, repo) : value));
2264
+ }
2265
+ function redactRepoMentions(text, repo) {
2266
+ const [owner, name] = repo.split("/");
2267
+ if (!owner || !name) {
2268
+ return text;
2269
+ }
2270
+ const replacement = "[redacted-authorized-repo]";
2271
+ let redacted = text
2272
+ .replace(new RegExp(`https://github\\.com/${escapeRegExp(owner)}/${escapeRegExp(name)}(?:\\.git)?`, "gi"), replacement)
2273
+ .replace(new RegExp(`git@github\\.com:${escapeRegExp(owner)}/${escapeRegExp(name)}(?:\\.git)?`, "gi"), replacement)
2274
+ .replace(new RegExp(`\\b${escapeRegExp(owner)}/${escapeRegExp(name)}\\b`, "gi"), replacement);
2275
+ if (name.length >= 4 && !isCommonRepoBasename(name)) {
2276
+ redacted = redacted.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, "gi"), replacement);
2277
+ }
2278
+ return redacted;
2279
+ }
2280
+ function redactPrivateRuntimeMentions(text, redaction) {
2281
+ let redacted = text;
2282
+ if (redaction?.repo) {
2283
+ redacted = redactRepoMentions(redacted, redaction.repo);
2284
+ }
2285
+ if (redaction?.providerRuntimeId) {
2286
+ redacted = redacted.replace(new RegExp(escapeRegExp(redaction.providerRuntimeId), "g"), "[redacted-provider-runtime-id]");
2287
+ }
2288
+ return redacted;
2289
+ }
2290
+ function isCommonRepoBasename(value) {
2291
+ return new Set(["app", "web", "api", "cli", "repo", "site", "docs", "main", "next", "demo", "test"]).has(value.toLowerCase());
2292
+ }
2293
+ function escapeRegExp(value) {
2294
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2295
+ }
2296
+ function hasAppUrlProofBlocker(text) {
2297
+ const normalized = normalizeActorEvidenceForPattern(text);
2298
+ const blocker = "(?:unknown option|unsupported|not available|does\\s+\\W*not\\W*(?:support|expose|accept)|did\\s+\\W*not\\W*(?:support|expose|accept)|doesnt\\s+(?:support|expose|accept)|didnt\\s+(?:support|expose|accept))";
2299
+ const appUrl = "(?:--app-url|run\\s+--app-url|app-url\\s+proof)";
2300
+ return new RegExp(`${blocker}[\\s\\S]{0,220}${appUrl}|${appUrl}[\\s\\S]{0,220}${blocker}`, "i").test(normalized);
2301
+ }
2302
+ function normalizeActorEvidenceForPattern(text) {
2303
+ return text
2304
+ .replace(/[*_`~[\](){}<>]/g, " ")
2305
+ .replace(/\s+/g, " ")
2306
+ .trim();
2307
+ }
2308
+ async function arrangeLiveDesktopForScreenshot(desktop, terminalTitle, requestTimeoutMs) {
2309
+ const command = buildRemoteScreenshotArrangeCommand(terminalTitle);
2310
+ await desktop.commands.run(`bash -lc ${shellQuote(command)}`, {
2311
+ requestTimeoutMs,
2312
+ timeoutMs: 15_000
2313
+ }).catch(() => undefined);
871
2314
  }
872
2315
  function parseRemoteCompletion(payload) {
873
2316
  try {
@@ -879,7 +2322,20 @@ function parseRemoteCompletion(payload) {
879
2322
  const nestedVerifyPassed = parsed.nestedVerifyStatus === "passed"
880
2323
  ? true
881
2324
  : parsed.nestedVerifyStatus === "failed" ? false : undefined;
2325
+ const appStatus = normalizeAppStatus(parsed.appStatus);
2326
+ const actorStatus = normalizeActorStatus(parsed.actorStatus);
2327
+ const visualStatus = normalizeVisualStatus(parsed.visualStatus);
882
2328
  return {
2329
+ ...(typeof parsed.actorLogPath === "string" && parsed.actorLogPath.trim() ? { actorLogPath: sanitizeRemoteLog(parsed.actorLogPath) } : {}),
2330
+ ...(typeof parsed.actorLogTail === "string" && parsed.actorLogTail.trim() ? { actorLogTail: sanitizeRemoteLog(parsed.actorLogTail) } : {}),
2331
+ ...(typeof parsed.actorLastMessageTail === "string" && parsed.actorLastMessageTail.trim() ? { actorLastMessageTail: sanitizeRemoteLog(parsed.actorLastMessageTail) } : {}),
2332
+ ...(typeof parsed.actorPid === "number" && Number.isFinite(parsed.actorPid) ? { actorPid: parsed.actorPid } : {}),
2333
+ ...(actorStatus ? { actorStatus } : {}),
2334
+ ...(typeof parsed.appLogPath === "string" && parsed.appLogPath.trim() ? { appLogPath: sanitizeRemoteLog(parsed.appLogPath) } : {}),
2335
+ ...(typeof parsed.appPid === "number" && Number.isFinite(parsed.appPid) ? { appPid: parsed.appPid } : {}),
2336
+ ...(typeof parsed.appReason === "string" && parsed.appReason.trim() ? { appReason: sanitizeRemoteLog(parsed.appReason).replace(/\s+/g, " ").slice(0, 240) } : {}),
2337
+ ...(appStatus ? { appStatus } : {}),
2338
+ ...(typeof parsed.appUrl === "string" && parsed.appUrl.trim() ? { appUrl: sanitizeRemoteLog(parsed.appUrl).replace(/\s+/g, " ").slice(0, 240) } : {}),
883
2339
  checkedAt: typeof parsed.completedAt === "string" ? parsed.completedAt : new Date().toISOString(),
884
2340
  ...(typeof parsed.exitCode === "number" ? { exitCode: parsed.exitCode } : {}),
885
2341
  ...(typeof parsed.logTail === "string" ? { logTail: sanitizeRemoteLog(parsed.logTail) } : {}),
@@ -888,13 +2344,47 @@ function parseRemoteCompletion(payload) {
888
2344
  reason: typeof parsed.reason === "string" && parsed.reason.trim()
889
2345
  ? sanitizeRemoteLog(parsed.reason).replace(/\s+/g, " ").slice(0, 240)
890
2346
  : defaultReasonForCompletion(status),
891
- status
2347
+ ...(isRunSetupQualitySnapshot(parsed.setupQuality) ? { setupQuality: sanitizeSetupQualitySnapshot(parsed.setupQuality) } : {}),
2348
+ status,
2349
+ ...(typeof parsed.visualReason === "string" && parsed.visualReason.trim() ? { visualReason: sanitizeRemoteLog(parsed.visualReason).replace(/\s+/g, " ").slice(0, 240) } : {}),
2350
+ ...(visualStatus ? { visualStatus } : {}),
2351
+ ...(typeof parsed.visualWindowCount === "number" && Number.isFinite(parsed.visualWindowCount) ? { visualWindowCount: parsed.visualWindowCount } : {})
892
2352
  };
893
2353
  }
894
2354
  catch {
895
2355
  return null;
896
2356
  }
897
2357
  }
2358
+ function normalizeAppStatus(value) {
2359
+ return value === "not_started"
2360
+ || value === "running"
2361
+ || value === "blocked"
2362
+ || value === "failed"
2363
+ || value === "missing"
2364
+ || value === "unknown"
2365
+ ? value
2366
+ : null;
2367
+ }
2368
+ function normalizeVisualStatus(value) {
2369
+ return value === "not_started"
2370
+ || value === "visible"
2371
+ || value === "blocked"
2372
+ || value === "unknown"
2373
+ ? value
2374
+ : null;
2375
+ }
2376
+ function normalizeActorStatus(value) {
2377
+ return value === "not_started"
2378
+ || value === "running"
2379
+ || value === "passed"
2380
+ || value === "failed"
2381
+ || value === "blocked"
2382
+ || value === "timed_out"
2383
+ || value === "suspended"
2384
+ || value === "unknown"
2385
+ ? value
2386
+ : null;
2387
+ }
898
2388
  function normalizeCompletionStatus(value) {
899
2389
  return value === "running"
900
2390
  || value === "passed"
@@ -918,12 +2408,139 @@ function defaultReasonForCompletion(status) {
918
2408
  return "Remote bootstrap is still running.";
919
2409
  }
920
2410
  }
921
- function sanitizeRemoteLog(value) {
922
- return value
923
- .replace(/sk-[A-Za-z0-9_-]{12,}/g, "[redacted-openai-key]")
924
- .replace(/e2b_[A-Za-z0-9_-]{12,}/g, "[redacted-e2b-key]")
925
- .replace(/gh[pousr]_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
2411
+ function isRunSetupQualitySnapshot(value) {
2412
+ if (!isRecord(value) || value.schema !== "mimetic.setup-quality.v1") {
2413
+ return false;
2414
+ }
2415
+ return Array.isArray(value.checks)
2416
+ && Array.isArray(value.tree)
2417
+ && Array.isArray(value.previews)
2418
+ && isRecord(value.mimetic)
2419
+ && isRecord(value.packageScripts)
2420
+ && typeof value.generatedAt === "string"
2421
+ && typeof value.summary === "string"
2422
+ && (value.status === "passed" || value.status === "needs_review" || value.status === "blocked");
2423
+ }
2424
+ function sanitizeSetupQualitySnapshot(snapshot) {
2425
+ const safeTree = snapshot.tree
2426
+ .filter((entry) => isSafeRepoRelativePath(entry.path) && (entry.type === "file" || entry.type === "directory"))
2427
+ .slice(0, 240)
2428
+ .map((entry) => ({
2429
+ path: sanitizeSetupQualityPath(entry.path),
2430
+ type: entry.type,
2431
+ ...(typeof entry.sizeBytes === "number" && Number.isFinite(entry.sizeBytes) ? { sizeBytes: Math.max(0, Math.round(entry.sizeBytes)) } : {})
2432
+ }));
2433
+ const safePreviews = snapshot.previews
2434
+ .filter((preview) => isSafeRepoRelativePath(preview.path))
2435
+ .slice(0, 20)
2436
+ .map((preview) => ({
2437
+ path: sanitizeSetupQualityPath(preview.path),
2438
+ language: sanitizePreviewLanguage(preview.language),
2439
+ truncated: preview.truncated === true,
2440
+ text: sanitizeSetupQualityText(preview.text).slice(0, 8_000)
2441
+ }));
2442
+ const safeScripts = {};
2443
+ for (const [key, value] of Object.entries(snapshot.packageScripts)) {
2444
+ const safeKey = sanitizeSetupQualityText(key).replace(/[^a-zA-Z0-9:_-]/g, "").slice(0, 80);
2445
+ if (!safeKey || typeof value !== "string") {
2446
+ continue;
2447
+ }
2448
+ safeScripts[safeKey] = sanitizeSetupQualityText(value).slice(0, 300);
2449
+ }
2450
+ return {
2451
+ schema: "mimetic.setup-quality.v1",
2452
+ generatedAt: sanitizeSetupQualityText(snapshot.generatedAt).slice(0, 80) || new Date().toISOString(),
2453
+ redaction: {
2454
+ status: "passed",
2455
+ rawPreviews: snapshot.redaction?.rawPreviews === "suppressed" ? "suppressed" : "included",
2456
+ notes: sanitizeSetupQualityText(snapshot.redaction?.notes ?? "Remote setup snapshot was redacted before persistence.").slice(0, 240)
2457
+ },
2458
+ summary: sanitizeSetupQualityText(snapshot.summary).slice(0, 320),
2459
+ status: snapshot.status,
2460
+ checks: snapshot.checks
2461
+ .filter((check) => isRecord(check) && typeof check.id === "string" && typeof check.label === "string" && typeof check.detail === "string")
2462
+ .slice(0, 40)
2463
+ .map((check) => ({
2464
+ id: sanitizeSetupQualityText(check.id).replace(/[^a-zA-Z0-9:_-]/g, "").slice(0, 80) || "check",
2465
+ label: sanitizeSetupQualityText(check.label).slice(0, 140),
2466
+ ok: check.ok === true,
2467
+ detail: sanitizeSetupQualityText(check.detail).slice(0, 320)
2468
+ })),
2469
+ tree: safeTree,
2470
+ previews: safePreviews,
2471
+ ...(snapshot.studyQuality ? { studyQuality: sanitizeStudyQualitySnapshot(snapshot.studyQuality) } : {}),
2472
+ packageScripts: safeScripts,
2473
+ mimetic: {
2474
+ configPresent: snapshot.mimetic.configPresent === true,
2475
+ personaCount: typeof snapshot.mimetic.personaCount === "number" && Number.isFinite(snapshot.mimetic.personaCount) ? Math.max(0, Math.round(snapshot.mimetic.personaCount)) : 0,
2476
+ scenarioCount: typeof snapshot.mimetic.scenarioCount === "number" && Number.isFinite(snapshot.mimetic.scenarioCount) ? Math.max(0, Math.round(snapshot.mimetic.scenarioCount)) : 0,
2477
+ packageScriptPresent: snapshot.mimetic.packageScriptPresent === true,
2478
+ gitignoreContainsRuntimeIgnore: snapshot.mimetic.gitignoreContainsRuntimeIgnore === true
2479
+ }
2480
+ };
2481
+ }
2482
+ function sanitizeStudyQualitySnapshot(studyQuality) {
2483
+ const rating = studyQuality.rating === "none"
2484
+ || studyQuality.rating === "ceremonial"
2485
+ || studyQuality.rating === "useful"
2486
+ || studyQuality.rating === "high_leverage"
2487
+ ? studyQuality.rating
2488
+ : "none";
2489
+ return {
2490
+ schema: "mimetic.study-quality.v1",
2491
+ rating,
2492
+ summary: sanitizeSetupQualityText(studyQuality.summary).slice(0, 320),
2493
+ checks: Array.isArray(studyQuality.checks)
2494
+ ? studyQuality.checks
2495
+ .filter((check) => isRecord(check) && typeof check.id === "string" && typeof check.label === "string" && typeof check.detail === "string")
2496
+ .slice(0, 20)
2497
+ .map((check) => ({
2498
+ id: sanitizeSetupQualityText(check.id).replace(/[^a-zA-Z0-9:_-]/g, "").slice(0, 80) || "study-check",
2499
+ label: sanitizeSetupQualityText(check.label).slice(0, 140),
2500
+ ok: check.ok === true,
2501
+ detail: sanitizeSetupQualityText(check.detail).slice(0, 320)
2502
+ }))
2503
+ : [],
2504
+ signals: {
2505
+ appUrlProofBlocked: studyQuality.signals?.appUrlProofBlocked === true,
2506
+ appUrlProofMentioned: studyQuality.signals?.appUrlProofMentioned === true,
2507
+ actorInsightCaptured: studyQuality.signals?.actorInsightCaptured === true,
2508
+ coverageCustomized: studyQuality.signals?.coverageCustomized === true,
2509
+ personaCustomized: studyQuality.signals?.personaCustomized === true,
2510
+ scenarioCustomized: studyQuality.signals?.scenarioCustomized === true
2511
+ }
2512
+ };
2513
+ }
2514
+ function sanitizePreviewLanguage(value) {
2515
+ return value === "json" || value === "yaml" || value === "typescript" || value === "markdown" || value === "text"
2516
+ ? value
2517
+ : "text";
2518
+ }
2519
+ function sanitizeSetupQualityPath(value) {
2520
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "").slice(0, 240);
2521
+ }
2522
+ function sanitizeSetupQualityText(value) {
2523
+ return redactOssRemoteTelemetryText(String(value ?? ""))
2524
+ .replace(/\0/g, "")
926
2525
  .replace(/https?:\/\/[^/\s]*e2b[^)\s]+/gi, "[redacted-e2b-url]")
2526
+ .trim();
2527
+ }
2528
+ function isSafeRepoRelativePath(value) {
2529
+ if (typeof value !== "string" || !value.trim()) {
2530
+ return false;
2531
+ }
2532
+ const normalized = value.replace(/\\/g, "/").replace(/^\.\/+/, "");
2533
+ return !path.isAbsolute(normalized)
2534
+ && !normalized.includes("://")
2535
+ && !normalized.startsWith("../")
2536
+ && !normalized.split("/").includes("..")
2537
+ && !normalized.split("/").some((segment) => /^(?:\.env(?:\..*)?|\.npmrc|\.git|node_modules|dist|build|\.next)$/.test(segment));
2538
+ }
2539
+ function isRecord(value) {
2540
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2541
+ }
2542
+ function sanitizeRemoteLog(value) {
2543
+ return redactOssRemoteTelemetryText(value)
927
2544
  .split(/\r?\n/)
928
2545
  .slice(-80)
929
2546
  .join("\n")
@@ -935,7 +2552,7 @@ async function loadE2BDesktopModule() {
935
2552
  }
936
2553
  catch (error) {
937
2554
  if (isMissingE2BDesktopDependency(error)) {
938
- throw new Error("Live E2B desktop launch requires optional peer dependency @e2b/desktop. Install it in this project with `npm i -D @e2b/desktop`, or run `mimetic lab oss --dry-run`.");
2555
+ throw new Error("Live E2B desktop launch requires optional peer dependency @e2b/desktop. Install it in this project with `npm i -D @e2b/desktop`, or run `mimetic lab run oss --dry-run`.");
939
2556
  }
940
2557
  throw error;
941
2558
  }
@@ -944,18 +2561,22 @@ function isMissingE2BDesktopDependency(error) {
944
2561
  const value = error;
945
2562
  return value.code === "ERR_MODULE_NOT_FOUND" && value.message?.includes("@e2b/desktop") === true;
946
2563
  }
947
- async function startOssBootstrap(desktop, assignment, localPackage, requestTimeoutMs) {
948
- const token = repoSlugToken(assignment.repo);
2564
+ async function startOssBootstrap(desktop, assignment, localPackage, requestTimeoutMs, display = {
2565
+ repoLabel: assignment.repo,
2566
+ token: repoSlugToken(assignment.repo)
2567
+ }) {
2568
+ const token = display.token;
949
2569
  const rootDir = `/home/user/mimetic-oss-lab/${token}`;
950
2570
  const remotePackagePath = `/tmp/${localPackage?.fileName ?? "mimetic-cli.tgz"}`;
2571
+ const remoteHostActorPlanPath = `${rootDir}/host-actor-plan.json`;
951
2572
  const bootstrapPath = `${rootDir}/bootstrap.sh`;
952
2573
  const launcherPath = `${rootDir}/launch-terminal.sh`;
953
2574
  const logPath = `${rootDir}/bootstrap.log`;
954
2575
  const completionPath = `${rootDir}/completion.json`;
955
2576
  const nestedObserverPath = `${rootDir}/repo/.mimetic/runs/nested-${token}/observer/index.html`;
956
- const title = `Mimetic ${assignment.index} ${assignment.repo}`;
2577
+ const title = `Mimetic ${assignment.index} ${display.repoLabel}`;
957
2578
  const baseTail = [
958
- `repo: ${assignment.repo}`,
2579
+ `repo: ${display.repoLabel}`,
959
2580
  `sandbox: ${desktop.sandboxId}`,
960
2581
  `remote package: ${localPackage ? remotePackagePath : "npm:mimetic-cli fallback"}`,
961
2582
  `bootstrap: ${bootstrapPath}`,
@@ -975,11 +2596,18 @@ async function startOssBootstrap(desktop, assignment, localPackage, requestTimeo
975
2596
  useOctetStream: true
976
2597
  });
977
2598
  }
2599
+ if (display.hostActorPlanResult?.plan) {
2600
+ await desktop.files.write(remoteHostActorPlanPath, `${JSON.stringify(display.hostActorPlanResult.plan, null, 2)}\n`, {
2601
+ requestTimeoutMs
2602
+ });
2603
+ }
978
2604
  const bootstrapScript = buildRemoteBootstrapScript({
979
2605
  assignment,
980
2606
  completionPath,
2607
+ displayRepo: display.repoLabel,
981
2608
  logPath,
982
2609
  nestedObserverPath,
2610
+ ...(display.hostActorPlanResult?.plan ? { remoteHostActorPlanPath } : {}),
983
2611
  rootDir,
984
2612
  token,
985
2613
  ...(localPackage ? { remotePackagePath } : {})
@@ -1015,9 +2643,10 @@ async function startOssBootstrap(desktop, assignment, localPackage, requestTimeo
1015
2643
  status: "started",
1016
2644
  tail: [
1017
2645
  "Visible E2B bootstrap terminal launched.",
1018
- "The terminal clones the public repo, installs this local mimetic-cli package tarball when available, runs nested Mimetic proof commands, attempts Codex TUI, then opens the nested Observer in Chrome.",
2646
+ "The terminal clones the authorized repo, installs this local mimetic-cli package tarball when available, runs nested Mimetic proof commands, attempts Codex TUI, then opens the nested Observer in Chrome.",
1019
2647
  baseTail
1020
- ].join("\n")
2648
+ ].join("\n"),
2649
+ terminalTitle: title
1021
2650
  };
1022
2651
  }
1023
2652
  catch (error) {
@@ -1033,7 +2662,8 @@ async function startOssBootstrap(desktop, assignment, localPackage, requestTimeo
1033
2662
  "Bootstrap launcher failed before the remote terminal could start.",
1034
2663
  baseTail,
1035
2664
  `error: ${compactError(error)}`
1036
- ].join("\n")
2665
+ ].join("\n"),
2666
+ terminalTitle: title
1037
2667
  };
1038
2668
  }
1039
2669
  }
@@ -1044,16 +2674,35 @@ function buildRemoteBootstrapScript(args) {
1044
2674
  set -Eeuo pipefail
1045
2675
  export TERM=xterm-256color
1046
2676
  export MIMETIC_PUBLIC_SAFE=1
2677
+ MIMETIC_PRIVATE_CODEX_API_KEY="\${MIMETIC_CODEX_API_KEY:-}"
2678
+ MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN="\${MIMETIC_CODEX_ACCESS_TOKEN:-}"
2679
+ MIMETIC_PRIVATE_GITHUB_TOKEN="\${MIMETIC_GITHUB_TOKEN:-}"
2680
+ unset MIMETIC_CODEX_API_KEY MIMETIC_CODEX_ACCESS_TOKEN MIMETIC_GITHUB_TOKEN
2681
+ unset OPENAI_API_KEY CODEX_API_KEY CODEX_ACCESS_TOKEN E2B_API_KEY GH_TOKEN GITHUB_TOKEN
1047
2682
  ROOT_DIR=${shellQuote(args.rootDir)}
1048
2683
  APP_DIR="$ROOT_DIR/repo"
1049
2684
  LOG_PATH=${shellQuote(args.logPath)}
1050
2685
  COMPLETION_PATH=${shellQuote(args.completionPath)}
1051
2686
  NESTED_OBSERVER=${shellQuote(args.nestedObserverPath)}
1052
2687
  REMOTE_PACKAGE=${args.remotePackagePath ? shellQuote(args.remotePackagePath) : "''"}
2688
+ HOST_ACTOR_PLAN=${args.remoteHostActorPlanPath ? shellQuote(args.remoteHostActorPlanPath) : "''"}
2689
+ APP_LOG_PATH="$ROOT_DIR/app.log"
2690
+ ACTOR_LOG_PATH="$ROOT_DIR/actor.log"
2691
+ ACTOR_LAST_MESSAGE_PATH="$ROOT_DIR/actor-last-message.txt"
2692
+ TERMINAL_TITLE=${shellQuote(`Mimetic ${args.assignment.index} ${args.displayRepo}`)}
1053
2693
  mkdir -p "$ROOT_DIR"
1054
2694
  touch "$LOG_PATH"
1055
2695
  exec > >(tee -a "$LOG_PATH") 2>&1
1056
2696
  NESTED_VERIFY_STATUS=not_run
2697
+ APP_STATUS=not_started
2698
+ APP_REASON="Target app startup has not started."
2699
+ APP_URL=""
2700
+ APP_PID=""
2701
+ ACTOR_STATUS=not_started
2702
+ ACTOR_PID=""
2703
+ VISUAL_STATUS=not_started
2704
+ VISUAL_REASON="Browser windows have not been arranged."
2705
+ VISUAL_WINDOW_COUNT=0
1057
2706
 
1058
2707
  write_completion() {
1059
2708
  local status="$1"
@@ -1064,31 +2713,260 @@ write_completion() {
1064
2713
  nested_observer_present=true
1065
2714
  fi
1066
2715
 
1067
- node - "$COMPLETION_PATH" "$LOG_PATH" "$status" "$reason" "$exit_code" "$nested_observer_present" "$NESTED_VERIFY_STATUS" <<'NODE' || true
2716
+ node - "$COMPLETION_PATH" "$LOG_PATH" "$APP_DIR" "$status" "$reason" "$exit_code" "$nested_observer_present" "$NESTED_VERIFY_STATUS" "$APP_STATUS" "$APP_REASON" "$APP_URL" "$APP_PID" "$APP_LOG_PATH" "$ACTOR_STATUS" "$ACTOR_PID" "$ACTOR_LOG_PATH" "$ACTOR_LAST_MESSAGE_PATH" "$VISUAL_STATUS" "$VISUAL_REASON" "$VISUAL_WINDOW_COUNT" <<'NODE' || true
1068
2717
  const fs = require("node:fs");
2718
+ const path = require("node:path");
1069
2719
  const [
1070
2720
  completionPath,
1071
2721
  logPath,
2722
+ appDir,
1072
2723
  status,
1073
2724
  reason,
1074
2725
  exitCode,
1075
2726
  nestedObserverPresent,
1076
- nestedVerifyStatus
2727
+ nestedVerifyStatus,
2728
+ appStatus,
2729
+ appReason,
2730
+ appUrl,
2731
+ appPid,
2732
+ appLogPath,
2733
+ actorStatus,
2734
+ actorPid,
2735
+ actorLogPath,
2736
+ actorLastMessagePath,
2737
+ visualStatus,
2738
+ visualReason,
2739
+ visualWindowCount
1077
2740
  ] = process.argv.slice(2);
1078
- const rawTail = fs.existsSync(logPath)
1079
- ? fs.readFileSync(logPath, "utf8").split(/\\r?\\n/).slice(-80).join("\\n")
2741
+ const tailFile = (filePath, lines = 80) => fs.existsSync(filePath)
2742
+ ? fs.readFileSync(filePath, "utf8").split(/\\r?\\n/).slice(-lines).join("\\n")
1080
2743
  : "";
1081
- const redactedTail = rawTail
1082
- .replace(/sk-[A-Za-z0-9_-]{12,}/g, "[redacted-openai-key]")
2744
+ const redactText = (value) => String(value || "")
2745
+ .replace(/sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g, "[redacted-openai-key]")
1083
2746
  .replace(/e2b_[A-Za-z0-9_-]{12,}/g, "[redacted-e2b-key]")
1084
- .replace(/gh[pousr]_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]");
2747
+ .replace(/(?:gh[pousr]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,})/g, "[redacted-github-token]")
2748
+ .replace(/\\bBearer\\s+[A-Za-z0-9._~+/=-]{12,}\\b/gi, "Bearer [redacted-token]")
2749
+ .replace(/https?:\\/\\/[^/\\s]*e2b[^)\\s]+/gi, "[redacted-e2b-url]");
2750
+ const redactedTail = redactText(tailFile(logPath, 80));
2751
+ const actorLogTail = redactText(tailFile(actorLogPath, 80));
2752
+ const actorLastMessageTail = redactText(tailFile(actorLastMessagePath, 40));
2753
+ const numberOrNull = (value) => /^\\d+$/.test(String(value || "")) ? Number(value) : null;
2754
+ const cleanText = (value) => redactText(value)
2755
+ .replace(/\\s+/g, " ")
2756
+ .trim()
2757
+ .slice(0, 240);
2758
+ const safeRel = (value) => {
2759
+ const normalized = String(value || "").replace(/\\\\/g, "/").replace(/^\\.\\/+/, "");
2760
+ if (!normalized || normalized.startsWith("../") || normalized.includes("://") || normalized.split("/").includes("..")) return "";
2761
+ return normalized;
2762
+ };
2763
+ const languageFor = (rel) => {
2764
+ if (rel.endsWith(".json")) return "json";
2765
+ if (rel.endsWith(".yaml") || rel.endsWith(".yml")) return "yaml";
2766
+ if (rel.endsWith(".ts") || rel.endsWith(".tsx")) return "typescript";
2767
+ if (rel.endsWith(".md") || rel.endsWith(".markdown")) return "markdown";
2768
+ return "text";
2769
+ };
2770
+ const shouldPreview = (rel) => rel === "package.json"
2771
+ || rel === ".gitignore"
2772
+ || rel === "mimetic/config.ts"
2773
+ || rel === "mimetic/coverage-map.md"
2774
+ || rel === "mimetic/coverage-matrix.md"
2775
+ || rel.startsWith("mimetic/personas/")
2776
+ || rel.startsWith("mimetic/scenarios/");
2777
+ const ignoredSegments = new Set([".git", "node_modules", ".mimetic", "dist", "build", ".next", "coverage", ".turbo", ".cache"]);
2778
+ const readTextLimited = (filePath, maxBytes = 12000) => {
2779
+ try {
2780
+ const buffer = fs.readFileSync(filePath);
2781
+ return redactText(buffer.slice(0, maxBytes).toString("utf8")).replace(/\\0/g, "");
2782
+ } catch {
2783
+ return "";
2784
+ }
2785
+ };
2786
+ const countFilesUnder = (root, prefix) => {
2787
+ try {
2788
+ return fs.readdirSync(path.join(root, prefix), { withFileTypes: true }).filter((entry) => entry.isFile()).length;
2789
+ } catch {
2790
+ return 0;
2791
+ }
2792
+ };
2793
+ const buildSetupQuality = (root) => {
2794
+ const tree = [];
2795
+ const previews = [];
2796
+ const walk = (dir, depth) => {
2797
+ if (depth > 4 || tree.length >= 240) return;
2798
+ let entries = [];
2799
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
2800
+ entries.sort((a, b) => a.name.localeCompare(b.name));
2801
+ for (const entry of entries) {
2802
+ if (tree.length >= 240) break;
2803
+ if (ignoredSegments.has(entry.name)) continue;
2804
+ const absolute = path.join(dir, entry.name);
2805
+ const rel = safeRel(path.relative(root, absolute));
2806
+ if (!rel) continue;
2807
+ if (entry.isDirectory()) {
2808
+ tree.push({ path: rel, type: "directory" });
2809
+ walk(absolute, depth + 1);
2810
+ continue;
2811
+ }
2812
+ if (!entry.isFile()) continue;
2813
+ const stats = fs.statSync(absolute);
2814
+ tree.push({ path: rel, type: "file", sizeBytes: stats.size });
2815
+ if (shouldPreview(rel) && previews.length < 20) {
2816
+ const text = readTextLimited(absolute);
2817
+ previews.push({ path: rel, language: languageFor(rel), truncated: stats.size > 12000 || text.length >= 8000, text: text.slice(0, 8000) });
2818
+ }
2819
+ }
2820
+ };
2821
+ const packageJsonPath = path.join(root, "package.json");
2822
+ let packageScripts = {};
2823
+ if (fs.existsSync(packageJsonPath)) {
2824
+ try {
2825
+ const parsedPackage = JSON.parse(readTextLimited(packageJsonPath, 20000));
2826
+ if (parsedPackage && typeof parsedPackage.scripts === "object" && parsedPackage.scripts) {
2827
+ packageScripts = Object.fromEntries(Object.entries(parsedPackage.scripts).filter(([, value]) => typeof value === "string"));
2828
+ }
2829
+ } catch {}
2830
+ }
2831
+ walk(root, 0);
2832
+ const readRel = (rel) => readTextLimited(path.join(root, rel), 20000);
2833
+ const listFilesUnder = (prefix) => {
2834
+ const absoluteRoot = path.join(root, prefix);
2835
+ const files = [];
2836
+ const visit = (dir, depth) => {
2837
+ if (depth > 2 || files.length >= 40) return;
2838
+ let entries = [];
2839
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
2840
+ entries.sort((a, b) => a.name.localeCompare(b.name));
2841
+ for (const entry of entries) {
2842
+ const absolute = path.join(dir, entry.name);
2843
+ const rel = safeRel(path.relative(root, absolute));
2844
+ if (!rel) continue;
2845
+ if (entry.isDirectory()) visit(absolute, depth + 1);
2846
+ else if (entry.isFile()) files.push(rel);
2847
+ }
2848
+ };
2849
+ visit(absoluteRoot, 0);
2850
+ return files;
2851
+ };
2852
+ const coverageMapText = readRel("mimetic/coverage-map.md");
2853
+ const coverageMatrixText = readRel("mimetic/coverage-matrix.md");
2854
+ const personaFiles = listFilesUnder(path.join("mimetic", "personas"));
2855
+ const scenarioFiles = listFilesUnder(path.join("mimetic", "scenarios"));
2856
+ const personaText = personaFiles.map((rel) => readRel(rel)).join("\\n");
2857
+ const scenarioText = scenarioFiles.map((rel) => readRel(rel)).join("\\n");
2858
+ const configText = readRel("mimetic/config.ts");
2859
+ const defaultPersonaFiles = new Set(["mimetic/personas/synthetic-new-user.yaml", "mimetic/personas/skeptical-power-user.yaml"]);
2860
+ const defaultScenarioFiles = new Set(["mimetic/scenarios/first-run-smoke.yaml", "mimetic/scenarios/onboarding-regression.yaml"]);
2861
+ const coverageCustomized = (
2862
+ Boolean(coverageMapText.trim())
2863
+ && !/Current starter coverage is intentionally minimal/i.test(coverageMapText)
2864
+ && /\\b(screen|route|role|state|path|flow|journey|surface|happy|sad|friction)\\b/i.test(coverageMapText)
2865
+ ) || (
2866
+ Boolean(coverageMatrixText.trim())
2867
+ && !/\\bstarter\\b/i.test(coverageMatrixText)
2868
+ && /\\b(screen|route|role|state|path|flow|journey|surface|happy|sad|friction)\\b/i.test(coverageMatrixText)
2869
+ );
2870
+ const personaCustomized = personaFiles.some((rel) => !defaultPersonaFiles.has(rel))
2871
+ || (Boolean(personaText.trim())
2872
+ && !/Synthetic New User|Skeptical Power User|privacy-safe first-time user evaluating the app/i.test(personaText)
2873
+ && /\\b(role|workflow|creator|operator|admin|guest|player|customer|power user|novice|skeptical|accessibility|persona)\\b/i.test(personaText));
2874
+ const scenarioCustomized = scenarioFiles.some((rel) => !defaultScenarioFiles.has(rel))
2875
+ || (Boolean(scenarioText.trim())
2876
+ && !/First-run smoke|Onboarding regression|Reach the first meaningful product state|Exercise onboarding friction/i.test(scenarioText)
2877
+ && /\\b(route|screen|journey|state|happy|sad|friction|error|empty|mobile|desktop|evidence|expectation)\\b/i.test(scenarioText));
2878
+ const actorEvidenceText = [actorLogTail, actorLastMessageTail, redactedTail, scenarioText, configText].join("\\n");
2879
+ const actorEvidenceNormalized = actorEvidenceText.replace(/[*_\`~[\\](){}<>]/g, " ").replace(/\\s+/g, " ").trim();
2880
+ const appUrlProofBlocked = /(?:unknown option|unsupported|not available|does\\s+\\W*not\\W*(?:support|expose|accept)|did\\s+\\W*not\\W*(?:support|expose|accept)|doesnt\\s+(?:support|expose|accept)|didnt\\s+(?:support|expose|accept))[\\s\\S]{0,220}(?:--app-url|run\\s+--app-url|app-url\\s+proof)|(?:--app-url|run\\s+--app-url|app-url\\s+proof)[\\s\\S]{0,220}(?:unknown option|unsupported|not available|does\\s+\\W*not\\W*(?:support|expose|accept)|did\\s+\\W*not\\W*(?:support|expose|accept)|doesnt\\s+(?:support|expose|accept)|didnt\\s+(?:support|expose|accept))/i.test(actorEvidenceNormalized);
2881
+ const appUrlProofMentioned = !appUrlProofBlocked && (
2882
+ nestedVerifyStatus === "passed"
2883
+ || /(?:mimetic\\s+run[\\s\\S]{0,160}--app-url|--app-url[\\s\\S]{0,160}mimetic\\s+run)/i.test(actorEvidenceText)
2884
+ );
2885
+ const actorInsightCaptured = /\\b(observed|found|friction|improvement|issue|gap|confusing|blocked|recommend|none observed|feedback)\\b/i.test([actorLogTail, actorLastMessageTail].join("\\n"));
2886
+ const gitignore = readTextLimited(path.join(root, ".gitignore"), 20000);
2887
+ const configPresent = fs.existsSync(path.join(root, "mimetic", "config.ts"));
2888
+ const personaCount = countFilesUnder(root, path.join("mimetic", "personas"));
2889
+ const scenarioCount = countFilesUnder(root, path.join("mimetic", "scenarios"));
2890
+ const packageScriptPresent = Object.entries(packageScripts).some(([key, value]) => key.includes("mimetic") || String(value).includes("mimetic"));
2891
+ const gitignoreContainsRuntimeIgnore = /(^|\\n)\\s*\\.mimetic\\/?\\s*(\\n|$)/.test(gitignore);
2892
+ const checks = [
2893
+ { id: "mimetic-config", label: "Mimetic config", ok: configPresent, detail: configPresent ? "mimetic/config.ts exists." : "mimetic/config.ts was not created." },
2894
+ { id: "personas", label: "Personas", ok: personaCount > 0, detail: personaCount + " persona file(s) detected." },
2895
+ { id: "scenarios", label: "Scenarios", ok: scenarioCount > 0, detail: scenarioCount + " scenario file(s) detected." },
2896
+ { id: "package-script", label: "Package script", ok: packageScriptPresent, detail: packageScriptPresent ? "package.json exposes a Mimetic script." : "package.json does not expose a Mimetic script." },
2897
+ { id: "runtime-ignore", label: "Runtime ignore", ok: gitignoreContainsRuntimeIgnore, detail: gitignoreContainsRuntimeIgnore ? ".gitignore ignores .mimetic/." : ".gitignore does not ignore .mimetic/." }
2898
+ ];
2899
+ const studyChecks = [
2900
+ { id: "coverage-customized", label: "Coverage customized", ok: coverageCustomized, detail: coverageCustomized ? "Coverage map or matrix appears app-aware." : "Coverage map/matrix still appears starter-level or absent." },
2901
+ { id: "personas-customized", label: "Personas customized", ok: personaCustomized, detail: personaCustomized ? "Personas appear app-specific or non-starter." : "Personas appear generic starter-level." },
2902
+ { id: "scenarios-customized", label: "Scenarios customized", ok: scenarioCustomized, detail: scenarioCustomized ? "Scenarios appear app-specific or non-starter." : "Scenarios appear generic starter-level." },
2903
+ { id: "app-url-proof", label: "App-url proof", ok: appUrlProofMentioned, detail: appUrlProofMentioned ? "Actor/setup evidence references app-url proof." : appUrlProofBlocked ? "Actor evidence reports that app-url proof was blocked." : "No app-url proof signal detected." },
2904
+ { id: "actor-insight", label: "Actor insight", ok: actorInsightCaptured, detail: actorInsightCaptured ? "Actor evidence mentions feedback, friction, coverage, or observed product behavior." : "Actor evidence does not capture product feedback or coverage insight." }
2905
+ ];
2906
+ const studySignalCount = studyChecks.filter((check) => check.ok).length;
2907
+ const usefulStudy = coverageCustomized && personaCustomized && scenarioCustomized;
2908
+ const concreteStudyInsight = actorInsightCaptured || appUrlProofBlocked;
2909
+ const studyRating = !configPresent || personaCount === 0 || scenarioCount === 0
2910
+ ? "none"
2911
+ : usefulStudy && concreteStudyInsight && (appUrlProofMentioned || appUrlProofBlocked)
2912
+ ? "high_leverage"
2913
+ : usefulStudy
2914
+ ? "useful"
2915
+ : "ceremonial";
2916
+ const failed = checks.filter((check) => !check.ok);
2917
+ return {
2918
+ schema: "mimetic.setup-quality.v1",
2919
+ generatedAt: new Date().toISOString(),
2920
+ redaction: {
2921
+ status: "passed",
2922
+ rawPreviews: "included",
2923
+ notes: "Only allowlisted setup files are previewed; generated state, browser profiles, secrets, .git, node_modules, and .mimetic are excluded."
2924
+ },
2925
+ summary: failed.length === 0 ? "Mimetic setup evidence looks complete." : failed.length + " setup-quality gap(s) need review.",
2926
+ status: failed.length === 0 ? "passed" : "needs_review",
2927
+ checks,
2928
+ tree,
2929
+ previews,
2930
+ studyQuality: {
2931
+ schema: "mimetic.study-quality.v1",
2932
+ rating: studyRating,
2933
+ summary: "Study-quality rating " + studyRating + " from " + studySignalCount + "/5 app-specific leverage signals" + (appUrlProofBlocked ? "; app-url proof path was blocked by actor evidence." : "."),
2934
+ checks: studyChecks,
2935
+ signals: {
2936
+ appUrlProofBlocked,
2937
+ appUrlProofMentioned,
2938
+ actorInsightCaptured,
2939
+ coverageCustomized,
2940
+ personaCustomized,
2941
+ scenarioCustomized
2942
+ }
2943
+ },
2944
+ packageScripts,
2945
+ mimetic: { configPresent, personaCount, scenarioCount, packageScriptPresent, gitignoreContainsRuntimeIgnore }
2946
+ };
2947
+ };
2948
+ const setupQuality = buildSetupQuality(appDir);
1085
2949
  fs.writeFileSync(completionPath, JSON.stringify({
1086
2950
  schema: "mimetic.oss-meta-bootstrap-completion.v1",
1087
2951
  status,
1088
2952
  reason,
1089
2953
  exitCode: Number(exitCode),
2954
+ appStatus,
2955
+ appReason: cleanText(appReason),
2956
+ appUrl: cleanText(appUrl),
2957
+ appPid: numberOrNull(appPid),
2958
+ appLogPath: cleanText(appLogPath),
2959
+ actorStatus,
2960
+ actorPid: numberOrNull(actorPid),
2961
+ actorLogPath: cleanText(actorLogPath),
2962
+ actorLogTail,
2963
+ actorLastMessageTail,
1090
2964
  nestedObserverPresent: nestedObserverPresent === "true",
1091
2965
  nestedVerifyStatus,
2966
+ setupQuality,
2967
+ visualStatus,
2968
+ visualReason: cleanText(visualReason),
2969
+ visualWindowCount: numberOrNull(visualWindowCount),
1092
2970
  logTail: redactedTail,
1093
2971
  completedAt: new Date().toISOString()
1094
2972
  }, null, 2) + "\\n");
@@ -1098,20 +2976,30 @@ NODE
1098
2976
  finish() {
1099
2977
  local exit_code="$?"
1100
2978
  trap - EXIT
1101
- if [[ "$exit_code" -eq 0 ]]; then
1102
- write_completion "passed" "Nested Mimetic proof completed and nested Observer path was checked." "$exit_code"
1103
- else
2979
+ if [[ "$exit_code" -ne 0 ]]; then
1104
2980
  write_completion "failed" "Bootstrap exited before nested Mimetic proof completed." "$exit_code"
2981
+ elif [[ "$NESTED_VERIFY_STATUS" != "passed" ]]; then
2982
+ write_completion "failed" "Nested Mimetic verification did not pass." "$exit_code"
2983
+ elif [[ "$APP_STATUS" != "running" ]]; then
2984
+ write_completion "blocked" "Nested Mimetic proof completed, but the target app surface was not proven running." "$exit_code"
2985
+ elif [[ "$VISUAL_STATUS" != "visible" ]]; then
2986
+ write_completion "blocked" "Nested Mimetic proof completed, but headed desktop visual layout was not proven visible." "$exit_code"
2987
+ elif [[ "\${MIMETIC_OSS_META_REQUIRE_ACTOR:-0}" == "1" && "$ACTOR_STATUS" != "passed" ]]; then
2988
+ write_completion "blocked" "Required Codex actor evidence did not reach a passed terminal status." "$exit_code"
2989
+ else
2990
+ write_completion "passed" "Target app surface, nested Mimetic proof, and nested Observer were checked." "$exit_code"
1105
2991
  fi
1106
2992
  exit "$exit_code"
1107
2993
  }
1108
2994
  trap finish EXIT
1109
2995
 
1110
2996
  echo "== mimetic oss meta-lab bootstrap =="
1111
- echo "repo=${args.assignment.repo}"
2997
+ echo "repo=${args.displayRepo}"
1112
2998
  echo "public_safe=1"
1113
- echo "E2B_API_KEY=$([[ -n "\${E2B_API_KEY:-}" ]] && echo present || echo missing)"
1114
- echo "OPENAI_API_KEY=$([[ -n "\${OPENAI_API_KEY:-}" ]] && echo present || echo missing)"
2999
+ echo "provider_secrets=isolated"
3000
+ echo "github_token=$([[ -n "$MIMETIC_PRIVATE_GITHUB_TOKEN" ]] && echo available-for-clone || echo absent)"
3001
+ echo "codex_actor_auth=$([[ -n "$MIMETIC_PRIVATE_CODEX_API_KEY$MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN" ]] && echo available-for-actor || echo absent)"
3002
+ echo "host_actor_plan=$([[ -f "$HOST_ACTOR_PLAN" ]] && echo available || echo absent)"
1115
3003
  echo
1116
3004
 
1117
3005
  need() {
@@ -1146,19 +3034,600 @@ ensure_node
1146
3034
  need node
1147
3035
  need npm
1148
3036
 
1149
- run_tui() {
1150
- local status=0
1151
- if [[ -r /dev/tty && -w /dev/tty ]]; then
1152
- timeout 90s "$@" </dev/tty >/dev/tty 2>&1 || status=$?
3037
+ wait_for_http() {
3038
+ local url="$1"
3039
+ local timeout_ms="$2"
3040
+ local interval_ms="$3"
3041
+ node - "$url" "$timeout_ms" "$interval_ms" <<'NODE'
3042
+ const [url, timeoutArg, intervalArg] = process.argv.slice(2);
3043
+ const timeoutMs = Number(timeoutArg);
3044
+ const intervalMs = Number(intervalArg);
3045
+ const startedAt = Date.now();
3046
+
3047
+ async function probe() {
3048
+ const controller = new AbortController();
3049
+ const timer = setTimeout(() => controller.abort(), Math.min(intervalMs, 5_000));
3050
+ try {
3051
+ const response = await fetch(url, { signal: controller.signal });
3052
+ return response.status < 500;
3053
+ } catch {
3054
+ return false;
3055
+ } finally {
3056
+ clearTimeout(timer);
3057
+ }
3058
+ }
3059
+
3060
+ while (Date.now() - startedAt <= timeoutMs) {
3061
+ if (await probe()) process.exit(0);
3062
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3063
+ }
3064
+ process.exit(1);
3065
+ NODE
3066
+ }
3067
+
3068
+ wait_for_any_http() {
3069
+ local timeout_ms="$1"
3070
+ local interval_ms="$2"
3071
+ shift 2
3072
+ node - "$timeout_ms" "$interval_ms" "$@" <<'NODE'
3073
+ const [timeoutArg, intervalArg, ...urls] = process.argv.slice(2);
3074
+ const timeoutMs = Number(timeoutArg);
3075
+ const intervalMs = Number(intervalArg);
3076
+ const startedAt = Date.now();
3077
+
3078
+ async function probe(url) {
3079
+ const controller = new AbortController();
3080
+ const timer = setTimeout(() => controller.abort(), Math.min(intervalMs, 5_000));
3081
+ try {
3082
+ const response = await fetch(url, { signal: controller.signal });
3083
+ return response.status < 500;
3084
+ } catch {
3085
+ return false;
3086
+ } finally {
3087
+ clearTimeout(timer);
3088
+ }
3089
+ }
3090
+
3091
+ while (Date.now() - startedAt <= timeoutMs) {
3092
+ for (const url of urls) {
3093
+ if (await probe(url)) {
3094
+ console.log(url);
3095
+ process.exit(0);
3096
+ }
3097
+ }
3098
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3099
+ }
3100
+ process.exit(1);
3101
+ NODE
3102
+ }
3103
+
3104
+ open_browser_url() {
3105
+ local url="$1"
3106
+ local profile="$2"
3107
+ local x="$3"
3108
+ local y="$4"
3109
+ local width="$5"
3110
+ local height="$6"
3111
+ if command -v google-chrome >/dev/null 2>&1; then
3112
+ nohup google-chrome --new-window --no-first-run --no-default-browser-check --disable-default-apps --user-data-dir="$ROOT_DIR/chrome-$profile" --window-position="$x,$y" --window-size="$width,$height" "$url" >/dev/null 2>&1 &
3113
+ elif command -v firefox >/dev/null 2>&1; then
3114
+ nohup firefox --width "$width" --height "$height" "$url" >/dev/null 2>&1 &
3115
+ else
3116
+ echo "browser_open=skipped profile=$profile url=$url"
3117
+ fi
3118
+ }
3119
+
3120
+ open_nested_observer() {
3121
+ local label="$1"
3122
+ echo
3123
+ echo "== $label =="
3124
+ if [[ -f "$NESTED_OBSERVER" ]]; then
3125
+ open_browser_url "file://$NESTED_OBSERVER" nested-observer 760 0 680 940
3126
+ else
3127
+ echo "Nested observer missing: $NESTED_OBSERVER"
3128
+ fi
3129
+ }
3130
+
3131
+ arrange_lab_windows() {
3132
+ echo
3133
+ echo "== visual desktop layout =="
3134
+ VISUAL_STATUS=unknown
3135
+ VISUAL_REASON="Window manager proof was not collected."
3136
+ VISUAL_WINDOW_COUNT=0
3137
+ if ! command -v xdotool >/dev/null 2>&1; then
3138
+ VISUAL_STATUS=unknown
3139
+ VISUAL_REASON="xdotool is unavailable; cannot prove headed browser window visibility."
3140
+ echo "visual_status=$VISUAL_STATUS reason=$VISUAL_REASON"
3141
+ return 0
3142
+ fi
3143
+
3144
+ local chrome_windows=()
3145
+ local observer_window=""
3146
+ for attempt in $(seq 1 40); do
3147
+ mapfile -t chrome_windows < <(xdotool search --onlyvisible --class google-chrome 2>/dev/null || true)
3148
+ observer_window="$(xdotool search --onlyvisible --name "Mimetic Observer" 2>/dev/null | tail -n 1 || true)"
3149
+ VISUAL_WINDOW_COUNT="\${#chrome_windows[@]}"
3150
+ if [[ "$VISUAL_WINDOW_COUNT" -ge 2 && -n "$observer_window" ]]; then
3151
+ break
3152
+ fi
3153
+ sleep 0.25
3154
+ done
3155
+
3156
+ if [[ -n "$observer_window" ]]; then
3157
+ xdotool windowsize "$observer_window" 680 933 >/dev/null 2>&1 || true
3158
+ xdotool windowmove "$observer_window" 760 27 >/dev/null 2>&1 || true
3159
+ fi
3160
+
3161
+ local slot=0
3162
+ local win
3163
+ for win in "\${chrome_windows[@]}"; do
3164
+ if [[ -n "$observer_window" && "$win" == "$observer_window" ]]; then
3165
+ continue
3166
+ fi
3167
+ if [[ "$slot" -eq 0 ]]; then
3168
+ xdotool windowsize "$win" 760 493 >/dev/null 2>&1 || true
3169
+ xdotool windowmove "$win" 0 27 >/dev/null 2>&1 || true
3170
+ elif [[ "$slot" -eq 1 ]]; then
3171
+ xdotool windowsize "$win" 430 420 >/dev/null 2>&1 || true
3172
+ xdotool windowmove "$win" 0 520 >/dev/null 2>&1 || true
3173
+ else
3174
+ xdotool windowsize "$win" 330 420 >/dev/null 2>&1 || true
3175
+ xdotool windowmove "$win" 430 520 >/dev/null 2>&1 || true
3176
+ fi
3177
+ slot=$((slot + 1))
3178
+ done
3179
+
3180
+ local terminal_window
3181
+ terminal_window="$(xdotool search --onlyvisible --name "$TERMINAL_TITLE" 2>/dev/null | head -n 1 || true)"
3182
+ if [[ -n "$terminal_window" ]]; then
3183
+ xdotool windowlower "$terminal_window" >/dev/null 2>&1 || xdotool windowminimize "$terminal_window" >/dev/null 2>&1 || true
3184
+ fi
3185
+ if [[ -n "$observer_window" ]]; then
3186
+ xdotool windowactivate "$observer_window" >/dev/null 2>&1 || true
3187
+ fi
3188
+
3189
+ mapfile -t chrome_windows < <(xdotool search --onlyvisible --class google-chrome 2>/dev/null || true)
3190
+ VISUAL_WINDOW_COUNT="\${#chrome_windows[@]}"
3191
+ if [[ "$VISUAL_WINDOW_COUNT" -ge 2 && -n "$observer_window" ]]; then
3192
+ VISUAL_STATUS=visible
3193
+ VISUAL_REASON="Detected $VISUAL_WINDOW_COUNT visible Chrome windows including nested Observer; app and Observer windows arranged for screenshot."
3194
+ else
3195
+ VISUAL_STATUS=blocked
3196
+ VISUAL_REASON="Expected app and nested Observer browser windows, detected $VISUAL_WINDOW_COUNT visible Chrome window(s)."
3197
+ fi
3198
+ echo "visual_status=$VISUAL_STATUS window_count=$VISUAL_WINDOW_COUNT reason=$VISUAL_REASON"
3199
+ }
3200
+
3201
+ detect_app_plan() {
3202
+ node <<'NODE'
3203
+ const fs = require("node:fs");
3204
+ const shell = (value) => "'" + String(value).replace(/'/g, "'\\"'\\"'") + "'";
3205
+ const emit = (key, value) => console.log(key + "=" + shell(value));
3206
+
3207
+ if (!fs.existsSync("package.json")) {
3208
+ console.log("APP_PLAN_OK='0'");
3209
+ emit("APP_PM", "npm");
3210
+ emit("APP_PLAN_REASON", "package.json not found");
3211
+ process.exit(0);
3212
+ }
3213
+
3214
+ let pkg;
3215
+ try {
3216
+ pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
3217
+ } catch {
3218
+ console.log("APP_PLAN_OK='0'");
3219
+ emit("APP_PM", "npm");
3220
+ emit("APP_PLAN_REASON", "package.json could not be parsed");
3221
+ process.exit(0);
3222
+ }
3223
+
3224
+ const scripts = pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {};
3225
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
3226
+ const lockfiles = fs.readdirSync(".").filter((name) => /^(pnpm-lock\\.yaml|yarn\\.lock|bun\\.lockb?|package-lock\\.json|npm-shrinkwrap\\.json)$/.test(name));
3227
+ const packageManagerHint = typeof pkg.packageManager === "string" ? pkg.packageManager : "";
3228
+ let pm = "npm";
3229
+ if (packageManagerHint.startsWith("pnpm@") || lockfiles.includes("pnpm-lock.yaml")) pm = "pnpm";
3230
+ else if (packageManagerHint.startsWith("yarn@") || lockfiles.includes("yarn.lock")) pm = "yarn";
3231
+ else if (packageManagerHint.startsWith("bun@") || lockfiles.includes("bun.lock") || lockfiles.includes("bun.lockb")) pm = "bun";
3232
+
3233
+ const names = ["dev", "start", "serve", "preview"];
3234
+ const entries = names
3235
+ .map((name) => ({ name, command: typeof scripts[name] === "string" ? scripts[name].trim() : "" }))
3236
+ .filter((entry) => entry.command && /^[A-Za-z0-9:_-]+$/.test(entry.name));
3237
+
3238
+ if (!entries.length) {
3239
+ console.log("APP_PLAN_OK='0'");
3240
+ emit("APP_PM", pm);
3241
+ emit("APP_PLAN_REASON", "no dev/start/serve/preview script found");
3242
+ process.exit(0);
3243
+ }
3244
+
3245
+ const pickByCommand = (pattern) => entries.find((entry) => pattern.test(entry.command));
3246
+ let picked = null;
3247
+ let framework = "generic";
3248
+ if ((picked = pickByCommand(/\\bnext\\s+(dev|start)\\b/)) || deps.next) framework = "next";
3249
+ if (!picked && (picked = pickByCommand(/\\bvite(\\s|$)/))) framework = "vite";
3250
+ if (!picked && deps.vite) { picked = entries.find((entry) => entry.name === "dev") || entries[0]; framework = "vite"; }
3251
+ if (!picked && deps.next) picked = entries.find((entry) => entry.name === "dev" || entry.name === "start") || entries[0];
3252
+ if (!picked && (picked = pickByCommand(/\\bastro\\s+dev\\b/))) framework = "vite";
3253
+ if (!picked) picked = entries.find((entry) => entry.name === "dev") || entries.find((entry) => entry.name === "start") || entries[0];
3254
+
3255
+ const command = picked.command;
3256
+ const portFromCommand = /(?:^|\\s)(?:--port|-p)\\s+([0-9]{2,5})(?:\\s|$)/.exec(command)?.[1]
3257
+ || /(?:^|\\s)PORT=([0-9]{2,5})(?:\\s|$)/.exec(command)?.[1];
3258
+ const defaultPort = framework === "vite" ? "5173" : "3000";
3259
+
3260
+ console.log("APP_PLAN_OK='1'");
3261
+ emit("APP_PM", pm);
3262
+ emit("APP_SCRIPT", picked.name);
3263
+ emit("APP_FRAMEWORK", framework);
3264
+ emit("APP_PORT", portFromCommand || defaultPort);
3265
+ emit("APP_PLAN_REASON", "selected " + picked.name + " script for " + framework + " app startup");
3266
+ NODE
3267
+ }
3268
+
3269
+ ensure_package_manager() {
3270
+ local pm="$1"
3271
+ mkdir -p "$ROOT_DIR/npm-global"
3272
+ export NPM_CONFIG_PREFIX="$ROOT_DIR/npm-global"
3273
+ export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
3274
+ if command -v "$pm" >/dev/null 2>&1; then
3275
+ return 0
3276
+ fi
3277
+ if command -v corepack >/dev/null 2>&1; then
3278
+ corepack enable >/dev/null 2>&1 || true
3279
+ if [[ "$pm" == "pnpm" ]]; then
3280
+ corepack prepare pnpm@latest --activate >/dev/null 2>&1 || true
3281
+ elif [[ "$pm" == "yarn" ]]; then
3282
+ corepack prepare yarn@stable --activate >/dev/null 2>&1 || true
3283
+ fi
3284
+ fi
3285
+ if command -v "$pm" >/dev/null 2>&1; then
3286
+ return 0
3287
+ fi
3288
+ case "$pm" in
3289
+ pnpm) npm i -g pnpm --no-audit --no-fund --prefix "$NPM_CONFIG_PREFIX" ;;
3290
+ yarn) npm i -g yarn --no-audit --no-fund --prefix "$NPM_CONFIG_PREFIX" ;;
3291
+ bun) npm i -g bun --no-audit --no-fund --prefix "$NPM_CONFIG_PREFIX" ;;
3292
+ npm) command -v npm >/dev/null 2>&1 ;;
3293
+ *) return 1 ;;
3294
+ esac
3295
+ }
3296
+
3297
+ install_project_dependencies() {
3298
+ local pm="$1"
3299
+ if [[ "$pm" == "pnpm" ]]; then
3300
+ pnpm config set verify-deps-before-run false --location project >/dev/null 2>&1 || true
3301
+ pnpm config set dangerously-allow-all-builds true --location project >/dev/null 2>&1 || true
3302
+ fi
3303
+ if [[ -d node_modules ]]; then
3304
+ echo "dependencies=present"
3305
+ return 0
3306
+ fi
3307
+ echo "dependencies=installing manager=$pm"
3308
+ case "$pm" in
3309
+ pnpm)
3310
+ echo "pnpm_build_scripts=allowed disposable_e2b_lab=1"
3311
+ if [[ -f pnpm-lock.yaml ]]; then pnpm install --frozen-lockfile --dangerously-allow-all-builds || pnpm install --dangerously-allow-all-builds; else pnpm install --dangerously-allow-all-builds; fi
3312
+ ;;
3313
+ yarn)
3314
+ if [[ -f yarn.lock ]]; then yarn install --frozen-lockfile --ignore-scripts || yarn install --ignore-scripts; else yarn install --ignore-scripts; fi
3315
+ ;;
3316
+ bun)
3317
+ bun install
3318
+ ;;
3319
+ npm)
3320
+ if [[ -f package-lock.json || -f npm-shrinkwrap.json ]]; then npm ci --ignore-scripts --no-audit --no-fund || npm install --ignore-scripts --no-audit --no-fund; else npm install --ignore-scripts --no-audit --no-fund; fi
3321
+ ;;
3322
+ *) return 1 ;;
3323
+ esac
3324
+ }
3325
+
3326
+ install_mimetic_cli() {
3327
+ echo
3328
+ echo "== installing mimetic-cli =="
3329
+ local plan_output
3330
+ plan_output="$(detect_app_plan)"
3331
+ eval "$plan_output"
3332
+ local pm="\${APP_PM:-npm}"
3333
+ if ! ensure_package_manager "$pm"; then
3334
+ echo "mimetic_install=blocked package_manager=$pm"
3335
+ return 1
3336
+ fi
3337
+ local spec="mimetic-cli"
3338
+ if [[ -n "$REMOTE_PACKAGE" && -f "$REMOTE_PACKAGE" ]]; then
3339
+ spec="$REMOTE_PACKAGE"
3340
+ fi
3341
+
3342
+ case "$pm" in
3343
+ pnpm)
3344
+ PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false npm_config_verify_deps_before_run=false pnpm add --save-dev --workspace-root "$spec" --ignore-scripts
3345
+ ;;
3346
+ yarn)
3347
+ YARN_ENABLE_SCRIPTS=0 yarn add -D "$spec"
3348
+ ;;
3349
+ bun)
3350
+ bun add -d "$spec"
3351
+ ;;
3352
+ npm)
3353
+ npm i -D "$spec" --ignore-scripts --no-audit --no-fund
3354
+ ;;
3355
+ *)
3356
+ return 1
3357
+ ;;
3358
+ esac
3359
+ }
3360
+
3361
+ apply_host_actor_plan() {
3362
+ if [[ "\${MIMETIC_OSS_META_HOST_CODEX_ACTOR:-0}" != "1" ]]; then
3363
+ return 0
3364
+ fi
3365
+
3366
+ echo
3367
+ echo "== host codex actor plan =="
3368
+ if [[ ! -f "$HOST_ACTOR_PLAN" ]]; then
3369
+ ACTOR_STATUS=blocked
3370
+ echo "host_actor_plan=missing"
3371
+ return 1
3372
+ fi
3373
+
3374
+ node - "$HOST_ACTOR_PLAN" <<'NODE'
3375
+ const fs = require("node:fs");
3376
+ const path = require("node:path");
3377
+ const [planPath] = process.argv.slice(2);
3378
+ const plan = JSON.parse(fs.readFileSync(planPath, "utf8"));
3379
+ if (plan.schema !== "mimetic.oss-host-actor-plan.v1" || plan.status !== "passed") {
3380
+ throw new Error("host actor plan is not passed");
3381
+ }
3382
+ const clean = (value, fallback) => String(value || fallback)
3383
+ .replace(/sk-[A-Za-z0-9_-]{20,}/g, "[redacted-openai-key]")
3384
+ .replace(/e2b_[A-Za-z0-9_-]{12,}/g, "[redacted-e2b-key]")
3385
+ .replace(/gh[pousr]_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
3386
+ .replace(/github_pat_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
3387
+ .replace(/\\s+/g, " ")
3388
+ .trim()
3389
+ .slice(0, 300);
3390
+ const token = (value, fallback) => clean(value, fallback).toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || fallback;
3391
+ const yamlScalar = (value) => clean(value, "").replace(/"/g, "\\\\\"");
3392
+ const yamlList = (values) => (Array.isArray(values) && values.length ? values : ["public_safe"]).map((value) => " - " + yamlScalar(value)).join("\\n");
3393
+ fs.mkdirSync("mimetic/personas", { recursive: true });
3394
+ fs.mkdirSync("mimetic/scenarios", { recursive: true });
3395
+ const personas = Array.isArray(plan.personas) ? plan.personas.slice(0, 2) : [];
3396
+ const scenarios = Array.isArray(plan.scenarios) ? plan.scenarios.slice(0, 2) : [];
3397
+ if (!personas.length || !scenarios.length) {
3398
+ throw new Error("host actor plan lacks personas or scenarios");
3399
+ }
3400
+ for (const persona of personas) {
3401
+ const id = token(persona.id, "host-codex-persona");
3402
+ fs.writeFileSync(path.join("mimetic/personas", id + ".yaml"), [
3403
+ "schema: mimetic.persona.v1",
3404
+ "id: " + id,
3405
+ "name: " + yamlScalar(persona.name || "Host Codex Persona"),
3406
+ "summary: " + yamlScalar(persona.intent || "Public-safe host Codex actor persona."),
3407
+ "traits:",
3408
+ yamlList(persona.traits),
3409
+ "constraints:",
3410
+ " - Do not use real personal data.",
3411
+ " - Do not use production accounts.",
3412
+ " - Treat all credentials as env var names only.",
3413
+ " - Source: local Codex host actor plan applied inside disposable E2B."
3414
+ ].join("\\n") + "\\n");
3415
+ }
3416
+ for (const scenario of scenarios) {
3417
+ const id = token(scenario.id, "host-codex-scenario");
3418
+ const personaId = token(personas[0].id, "host-codex-persona");
3419
+ fs.writeFileSync(path.join("mimetic/scenarios", id + ".yaml"), [
3420
+ "schema: mimetic.scenario.v1",
3421
+ "id: " + id,
3422
+ "title: " + yamlScalar(scenario.title || "Host Codex Scenario"),
3423
+ "persona: " + personaId,
3424
+ "goal: " + yamlScalar(scenario.goal || "Exercise the app through a public-safe browser flow."),
3425
+ "mode: browser",
3426
+ "steps:",
3427
+ ...(Array.isArray(scenario.steps) && scenario.steps.length ? scenario.steps : ["Open the app", "Reach a meaningful state"]).slice(0, 8).map((step, index) => [
3428
+ " - name: Step " + String(index + 1),
3429
+ " expectation: " + yamlScalar(step)
3430
+ ].join("\\n"))
3431
+ ].join("\\n") + "\\n");
3432
+ }
3433
+ console.log("host_actor_plan=applied personas=" + personas.length + " scenarios=" + scenarios.length);
3434
+ console.log("host_actor_recommended_proof=" + clean(plan.recommendedProof, "mimetic run --app-url <loopback-url> --sims 2"));
3435
+ NODE
3436
+ local apply_exit=$?
3437
+ if [[ "$apply_exit" -eq 0 ]]; then
3438
+ ACTOR_STATUS=blocked
3439
+ echo "actor_status=$ACTOR_STATUS source=host-codex-plan reason=remote-actor-not-run"
3440
+ else
3441
+ ACTOR_STATUS=failed
3442
+ echo "actor_status=$ACTOR_STATUS source=host-codex-plan exit=$apply_exit"
3443
+ fi
3444
+ return "$apply_exit"
3445
+ }
3446
+
3447
+ start_target_app_surface() {
3448
+ echo
3449
+ echo "== target app surface =="
3450
+ local plan_output
3451
+ plan_output="$(detect_app_plan)"
3452
+ eval "$plan_output"
3453
+ if [[ "\${APP_PLAN_OK:-0}" != "1" ]]; then
3454
+ APP_STATUS=missing
3455
+ APP_REASON="\${APP_PLAN_REASON:-no runnable app script detected}"
3456
+ echo "app_status=$APP_STATUS reason=$APP_REASON"
3457
+ return 0
3458
+ fi
3459
+
3460
+ if ! ensure_package_manager "$APP_PM"; then
3461
+ APP_STATUS=blocked
3462
+ APP_REASON="package manager $APP_PM is unavailable"
3463
+ echo "app_status=$APP_STATUS reason=$APP_REASON"
3464
+ return 0
3465
+ fi
3466
+
3467
+ if ! install_project_dependencies "$APP_PM"; then
3468
+ APP_STATUS=blocked
3469
+ APP_REASON="dependency install failed before app startup"
3470
+ echo "app_status=$APP_STATUS reason=$APP_REASON"
3471
+ return 0
3472
+ fi
3473
+
3474
+ local app_url_candidates=("http://localhost:$APP_PORT" "http://127.0.0.1:$APP_PORT" "http://[::1]:$APP_PORT")
3475
+ APP_URL="\${app_url_candidates[0]}"
3476
+ local start_command
3477
+ local command_prefix="HOST=0.0.0.0 PORT=$APP_PORT"
3478
+ local script_arg_separator="--"
3479
+ if [[ "$APP_PM" == "pnpm" ]]; then
3480
+ command_prefix="PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false npm_config_verify_deps_before_run=false $command_prefix"
3481
+ script_arg_separator=""
3482
+ fi
3483
+ case "$APP_FRAMEWORK" in
3484
+ next)
3485
+ start_command="$command_prefix $APP_PM run $APP_SCRIPT $script_arg_separator --hostname 0.0.0.0 --port $APP_PORT"
3486
+ ;;
3487
+ vite)
3488
+ start_command="$command_prefix $APP_PM run $APP_SCRIPT $script_arg_separator --host 0.0.0.0 --port $APP_PORT"
3489
+ ;;
3490
+ *)
3491
+ start_command="$command_prefix $APP_PM run $APP_SCRIPT"
3492
+ ;;
3493
+ esac
3494
+
3495
+ echo "app_plan=$APP_PLAN_REASON"
3496
+ echo "app_url_candidates=\${app_url_candidates[*]}"
3497
+ echo "app_command=$start_command"
3498
+ nohup bash -lc "$start_command" > "$APP_LOG_PATH" 2>&1 &
3499
+ APP_PID=$!
3500
+
3501
+ local ready_url=""
3502
+ if ready_url="$(wait_for_any_http 120000 1000 "\${app_url_candidates[@]}")"; then
3503
+ APP_URL="$ready_url"
3504
+ APP_STATUS=running
3505
+ APP_REASON="target app responded at $APP_URL"
3506
+ open_browser_url "$APP_URL" app-desktop 0 0 760 520
3507
+ open_browser_url "$APP_URL" app-compact 0 520 430 420
3508
+ else
3509
+ APP_STATUS=blocked
3510
+ APP_REASON="target app did not become HTTP-ready at $APP_URL"
3511
+ tail -n 40 "$APP_LOG_PATH" || true
3512
+ fi
3513
+ echo "app_status=$APP_STATUS reason=$APP_REASON pid=$APP_PID"
3514
+ }
3515
+
3516
+ start_actor_attempt() {
3517
+ echo
3518
+ echo "== codex actor attempt =="
3519
+ local actor_script="$ROOT_DIR/actor-run.sh"
3520
+ cat > "$actor_script" <<ACTOR
3521
+ #!/usr/bin/env bash
3522
+ set +e
3523
+ APP_DIR="$APP_DIR"
3524
+ if [[ "\${MIMETIC_OSS_META_ACTOR_FIRST:-0}" == "1" ]]; then
3525
+ PROMPT='You are a Mimetic meta-lab actor running in a disposable public-safe repo clone. Your goal is useful product-specific user-study setup, not ceremonial install proof. Inspect package.json, README, routes, scripts, and the running app shape. Install mimetic-cli as a dev dependency with the repo package manager if needed. Run npx mimetic init --yes if Mimetic is not initialized. Replace starter mimetic/coverage-map.md and mimetic/coverage-matrix.md with app-aware screens, roles, states, happy paths, and at least one sad/friction path. Author at least two public-safe app-specific personas and two desktop/mobile browser scenarios; avoid generic first-run-smoke only. Start the local app if feasible. Run npx mimetic run --help and verify --app-url is available. If the app is running locally, run npx mimetic run --app-url <loopback-url> --sims 2; do not use mimetic watch --sims as app behavior proof. After run --app-url, render or open the nested Mimetic Observer with npx mimetic watch --run latest --detach --no-open --json if feasible. Final summary must include personas/scenarios created, product journeys covered, one observed friction/improvement or none observed, and evidence paths. Do not stop at install/init proof. Do not wait on long-running watchers after rendering proof. Do not print secrets, do not commit, do not push, and do not file issues.'
3526
+ else
3527
+ PROMPT='You are a Mimetic meta-lab actor. Inspect this repo, inspect the running app and Mimetic artifacts already generated here, and draft the best next public-safe personas and desktop/mobile browser scenarios for human users of this app. Once the draft is written, exit successfully. Do not wait on long-running watchers. Do not print secrets, do not commit, do not push, and do not file issues.'
3528
+ fi
3529
+ printf -v app_dir_q '%q' "\\$APP_DIR"
3530
+ printf -v prompt_q '%q' "\\$PROMPT"
3531
+ printf -v actor_message_q '%q' "$ACTOR_LAST_MESSAGE_PATH"
3532
+ ACTOR_MODEL="\${MIMETIC_OSS_META_ACTOR_MODEL:-gpt-5.4-mini}"
3533
+ ACTOR_TIMEOUT_MS="\${MIMETIC_OSS_META_ACTOR_TIMEOUT_MS:-240000}"
3534
+ ACTOR_TIMEOUT_SECONDS=\\$(( (ACTOR_TIMEOUT_MS + 999) / 1000 ))
3535
+ printf -v actor_model_q '%q' "\\$ACTOR_MODEL"
3536
+ CODEX_COMMAND="npx -y @openai/codex@latest exec --ephemeral --ignore-user-config --skip-git-repo-check -m \\$actor_model_q -C \\$app_dir_q --dangerously-bypass-approvals-and-sandbox --output-last-message \\$actor_message_q \\$prompt_q"
3537
+ if [[ -n "\${MIMETIC_PRIVATE_CODEX_API_KEY:-}" && -n "\${MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN:-}" ]]; then
3538
+ CODEX_API_KEY="\\$MIMETIC_PRIVATE_CODEX_API_KEY" CODEX_ACCESS_TOKEN="\\$MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN" timeout "\\$ACTOR_TIMEOUT_SECONDS" bash -lc "\\$CODEX_COMMAND"
3539
+ elif [[ -n "\${MIMETIC_PRIVATE_CODEX_API_KEY:-}" ]]; then
3540
+ CODEX_API_KEY="\\$MIMETIC_PRIVATE_CODEX_API_KEY" timeout "\\$ACTOR_TIMEOUT_SECONDS" bash -lc "\\$CODEX_COMMAND"
3541
+ elif [[ -n "\${MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN:-}" ]]; then
3542
+ CODEX_ACCESS_TOKEN="\\$MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN" timeout "\\$ACTOR_TIMEOUT_SECONDS" bash -lc "\\$CODEX_COMMAND"
3543
+ else
3544
+ timeout "\\$ACTOR_TIMEOUT_SECONDS" bash -lc "\\$CODEX_COMMAND"
3545
+ fi
3546
+ code=\\$?
3547
+ echo "actor_exit=\\$code"
3548
+ exit "\\$code"
3549
+ ACTOR
3550
+ chmod +x "$actor_script"
3551
+ MIMETIC_PRIVATE_CODEX_API_KEY="$MIMETIC_PRIVATE_CODEX_API_KEY" MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN="$MIMETIC_PRIVATE_CODEX_ACCESS_TOKEN" nohup bash "$actor_script" > "$ACTOR_LOG_PATH" 2>&1 &
3552
+ ACTOR_PID=$!
3553
+ ACTOR_STATUS=running
3554
+ echo "actor_status=$ACTOR_STATUS pid=$ACTOR_PID log=$ACTOR_LOG_PATH"
3555
+ }
3556
+
3557
+ wait_for_actor_attempt_if_required() {
3558
+ if [[ "\${MIMETIC_OSS_META_REQUIRE_ACTOR:-0}" != "1" ]]; then
3559
+ return 0
3560
+ fi
3561
+
3562
+ echo
3563
+ echo "== required codex actor readback =="
3564
+ if [[ -z "$ACTOR_PID" ]]; then
3565
+ ACTOR_STATUS=blocked
3566
+ echo "actor_status=$ACTOR_STATUS reason=no actor process was started"
3567
+ return 1
3568
+ fi
3569
+
3570
+ local timeout_ms="\${MIMETIC_OSS_META_ACTOR_TIMEOUT_MS:-240000}"
3571
+ local started_ms
3572
+ started_ms="$(date +%s%3N)"
3573
+ while kill -0 "$ACTOR_PID" >/dev/null 2>&1; do
3574
+ local now_ms
3575
+ now_ms="$(date +%s%3N)"
3576
+ if [[ $((now_ms - started_ms)) -ge "$timeout_ms" ]]; then
3577
+ ACTOR_STATUS=timed_out
3578
+ kill "$ACTOR_PID" >/dev/null 2>&1 || true
3579
+ echo "actor_status=$ACTOR_STATUS timeout_ms=$timeout_ms log=$ACTOR_LOG_PATH"
3580
+ return 1
3581
+ fi
3582
+ sleep 1
3583
+ done
3584
+
3585
+ local actor_exit=0
3586
+ wait "$ACTOR_PID" || actor_exit=$?
3587
+ if [[ "$actor_exit" -eq 0 ]]; then
3588
+ ACTOR_STATUS=passed
1153
3589
  else
1154
- timeout 90s "$@" || status=$?
3590
+ ACTOR_STATUS=failed
1155
3591
  fi
1156
- echo "codex_tui_exit=$status"
1157
- return 0
3592
+ echo "actor_status=$ACTOR_STATUS exit=$actor_exit log=$ACTOR_LOG_PATH"
3593
+ echo "actor_log_tail_begin"
3594
+ tail -n 80 "$ACTOR_LOG_PATH" || true
3595
+ echo "actor_log_tail_end"
3596
+ [[ "$actor_exit" -eq 0 ]]
1158
3597
  }
1159
3598
 
1160
3599
  rm -rf "$APP_DIR"
1161
- git clone --depth=1 ${shellQuote(repoUrl)} "$APP_DIR"
3600
+ ASKPASS="$ROOT_DIR/git-askpass.sh"
3601
+ cat > "$ASKPASS" <<'ASKPASS'
3602
+ #!/usr/bin/env bash
3603
+ case "$1" in
3604
+ *Username*) echo "x-access-token" ;;
3605
+ *Password*) echo "\${MIMETIC_GITHUB_TOKEN_RUNTIME:-}" ;;
3606
+ *) echo "" ;;
3607
+ esac
3608
+ ASKPASS
3609
+ chmod 700 "$ASKPASS"
3610
+ clone_repo() {
3611
+ local repo_url=${shellQuote(repoUrl)}
3612
+ if GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 GIT_ASKPASS=false SSH_ASKPASS=false GIT_TERMINAL_PROMPT=0 git -c credential.helper= clone --depth=1 "$repo_url" "$APP_DIR"; then
3613
+ echo "clone_auth=anonymous"
3614
+ return 0
3615
+ fi
3616
+ echo "clone_auth=anonymous_failed retry=token_clone"
3617
+ rm -rf "$APP_DIR"
3618
+
3619
+ if [[ -n "$MIMETIC_PRIVATE_GITHUB_TOKEN" ]]; then
3620
+ if GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 MIMETIC_GITHUB_TOKEN_RUNTIME="$MIMETIC_PRIVATE_GITHUB_TOKEN" git -c credential.helper= clone --depth=1 "$repo_url" "$APP_DIR"; then
3621
+ echo "clone_auth=token"
3622
+ return 0
3623
+ fi
3624
+ echo "clone_auth=token_failed"
3625
+ rm -rf "$APP_DIR"
3626
+ fi
3627
+
3628
+ return 1
3629
+ }
3630
+ clone_repo
1162
3631
  cd "$APP_DIR"
1163
3632
  echo
1164
3633
  echo "== repo fingerprint =="
@@ -1167,20 +3636,40 @@ node --version || true
1167
3636
  npm --version || true
1168
3637
 
1169
3638
  echo
1170
- echo "== installing mimetic-cli =="
1171
- if [[ -n "$REMOTE_PACKAGE" && -f "$REMOTE_PACKAGE" ]]; then
1172
- npm i -D "$REMOTE_PACKAGE" --ignore-scripts --no-audit --no-fund
3639
+ echo "== mimetic skill discovery =="
3640
+ if npx -y skills add danielgwilson/mimetic-cli --skill mimetic-cli --list >/tmp/mimetic-skill-list.txt 2>&1; then
3641
+ echo "skill_discovery=listed"
3642
+ elif npx -y skills add danielgwilson/mimetic-cli --skill mimetic-cli >/tmp/mimetic-skill-install.txt 2>&1; then
3643
+ echo "skill_install=attempted"
1173
3644
  else
1174
- npm i -D mimetic-cli --ignore-scripts --no-audit --no-fund
3645
+ echo "skill_install=blocked"
3646
+ tail -n 20 /tmp/mimetic-skill-list.txt /tmp/mimetic-skill-install.txt 2>/dev/null || true
1175
3647
  fi
1176
3648
 
3649
+ echo
3650
+ if [[ "\${MIMETIC_OSS_META_ACTOR_FIRST:-0}" == "1" && "\${MIMETIC_OSS_META_HOST_CODEX_ACTOR:-0}" != "1" ]]; then
3651
+ start_actor_attempt
3652
+ wait_for_actor_attempt_if_required || true
3653
+ fi
3654
+
3655
+ echo
3656
+ install_mimetic_cli
3657
+
1177
3658
  echo
1178
3659
  echo "== mimetic init =="
1179
3660
  npx mimetic init --yes
3661
+ apply_host_actor_plan || true
3662
+
3663
+ start_target_app_surface
1180
3664
 
1181
3665
  echo
1182
3666
  echo "== nested mimetic proof =="
1183
- npx mimetic run --dry-run --run-id ${shellQuote(runId)}
3667
+ if [[ "$APP_STATUS" == "running" && -n "$APP_URL" ]]; then
3668
+ npx mimetic run --app-url "$APP_URL" --sims 2 --run-id ${shellQuote(runId)}
3669
+ else
3670
+ echo "app_not_running_for_browser_proof=$APP_REASON"
3671
+ npx mimetic run --dry-run --run-id ${shellQuote(runId)}
3672
+ fi
1184
3673
  if npx mimetic verify --run latest; then
1185
3674
  NESTED_VERIFY_STATUS=passed
1186
3675
  else
@@ -1188,31 +3677,18 @@ else
1188
3677
  exit 1
1189
3678
  fi
1190
3679
  npx mimetic watch --run latest --detach --no-open
1191
-
1192
- echo
1193
- echo "== optional codex tui attempt =="
1194
- if command -v codex >/dev/null 2>&1; then
1195
- run_tui codex --no-alt-screen -C "$APP_DIR" --sandbox danger-full-access --ask-for-approval never "You are a Mimetic OSS meta-lab actor. Inspect this public repo, inspect the Mimetic artifacts already generated here, and explain the best next persona/scenario work. Do not print secrets, do not commit, do not push, and do not file issues."
1196
- else
1197
- run_tui npx -y @openai/codex@latest --no-alt-screen -C "$APP_DIR" --sandbox danger-full-access --ask-for-approval never "You are a Mimetic OSS meta-lab actor. Inspect this public repo, inspect the Mimetic artifacts already generated here, and explain the best next persona/scenario work. Do not print secrets, do not commit, do not push, and do not file issues."
1198
- fi
1199
-
1200
- echo
1201
- echo "== opening nested observer =="
1202
- if [[ -f "$NESTED_OBSERVER" ]]; then
1203
- if command -v google-chrome >/dev/null 2>&1; then
1204
- nohup google-chrome --no-first-run --no-default-browser-check --disable-default-apps --user-data-dir="$ROOT_DIR/chrome-profile" "file://$NESTED_OBSERVER" >/dev/null 2>&1 &
1205
- elif command -v firefox >/dev/null 2>&1; then
1206
- nohup firefox "file://$NESTED_OBSERVER" >/dev/null 2>&1 &
1207
- else
1208
- echo "No browser command found. Nested observer is at: $NESTED_OBSERVER"
1209
- fi
1210
- else
1211
- echo "Nested observer missing: $NESTED_OBSERVER"
3680
+ open_nested_observer "opening nested observer"
3681
+ arrange_lab_windows
3682
+ if [[ "\${MIMETIC_OSS_META_ACTOR_FIRST:-0}" != "1" && "\${MIMETIC_OSS_META_HOST_CODEX_ACTOR:-0}" != "1" ]]; then
3683
+ start_actor_attempt
3684
+ wait_for_actor_attempt_if_required || true
1212
3685
  fi
1213
3686
 
1214
3687
  echo
1215
3688
  echo "== bootstrap complete =="
3689
+ echo "app_status=$APP_STATUS"
3690
+ echo "app_url=$APP_URL"
3691
+ echo "actor_status=$ACTOR_STATUS"
1216
3692
  echo "nested_observer=$NESTED_OBSERVER"
1217
3693
  `;
1218
3694
  }
@@ -1250,6 +3726,53 @@ for attempt in $(seq 1 20); do
1250
3726
  done
1251
3727
  exit 0`;
1252
3728
  }
3729
+ function buildRemoteScreenshotArrangeCommand(terminalTitle) {
3730
+ return `TERMINAL_TITLE=${shellQuote(terminalTitle ?? "")}
3731
+ if ! command -v xdotool >/dev/null 2>&1; then
3732
+ exit 0
3733
+ fi
3734
+ for attempt in $(seq 1 24); do
3735
+ CHROME_COUNT="$(xdotool search --onlyvisible --class google-chrome 2>/dev/null | wc -l | tr -d ' ')"
3736
+ OBSERVER_WINDOW="$(xdotool search --onlyvisible --name "Mimetic Observer" 2>/dev/null | tail -n 1 || true)"
3737
+ if [[ "$CHROME_COUNT" -ge 2 && -n "$OBSERVER_WINDOW" ]]; then
3738
+ break
3739
+ fi
3740
+ sleep 0.25
3741
+ done
3742
+ OBSERVER_WINDOW="$(xdotool search --onlyvisible --name "Mimetic Observer" 2>/dev/null | tail -n 1 || true)"
3743
+ if [[ -n "$OBSERVER_WINDOW" ]]; then
3744
+ xdotool windowsize "$OBSERVER_WINDOW" 680 933 >/dev/null 2>&1 || true
3745
+ xdotool windowmove "$OBSERVER_WINDOW" 760 27 >/dev/null 2>&1 || true
3746
+ fi
3747
+ slot=0
3748
+ while IFS= read -r win; do
3749
+ [[ -z "$win" ]] && continue
3750
+ if [[ -n "$OBSERVER_WINDOW" && "$win" == "$OBSERVER_WINDOW" ]]; then
3751
+ continue
3752
+ fi
3753
+ if [[ "$slot" -eq 0 ]]; then
3754
+ xdotool windowsize "$win" 760 493 >/dev/null 2>&1 || true
3755
+ xdotool windowmove "$win" 0 27 >/dev/null 2>&1 || true
3756
+ elif [[ "$slot" -eq 1 ]]; then
3757
+ xdotool windowsize "$win" 430 420 >/dev/null 2>&1 || true
3758
+ xdotool windowmove "$win" 0 520 >/dev/null 2>&1 || true
3759
+ else
3760
+ xdotool windowsize "$win" 330 420 >/dev/null 2>&1 || true
3761
+ xdotool windowmove "$win" 430 520 >/dev/null 2>&1 || true
3762
+ fi
3763
+ slot=$((slot + 1))
3764
+ done < <(xdotool search --onlyvisible --class google-chrome 2>/dev/null || true)
3765
+ if [[ -n "$TERMINAL_TITLE" ]]; then
3766
+ TERMINAL_WINDOW="$(xdotool search --onlyvisible --name "$TERMINAL_TITLE" 2>/dev/null | head -n 1 || true)"
3767
+ if [[ -n "$TERMINAL_WINDOW" ]]; then
3768
+ xdotool windowlower "$TERMINAL_WINDOW" >/dev/null 2>&1 || xdotool windowminimize "$TERMINAL_WINDOW" >/dev/null 2>&1 || true
3769
+ fi
3770
+ fi
3771
+ if [[ -n "$OBSERVER_WINDOW" ]]; then
3772
+ xdotool windowactivate "$OBSERVER_WINDOW" >/dev/null 2>&1 || true
3773
+ fi
3774
+ exit 0`;
3775
+ }
1253
3776
  async function runDesktopCommand(desktop, command, options) {
1254
3777
  const result = await desktop.commands.run(`bash -lc ${shellQuote(command)}`, options);
1255
3778
  if (result.exitCode && result.exitCode !== 0) {
@@ -1299,19 +3822,29 @@ function shellQuote(value) {
1299
3822
  }
1300
3823
  return `'${value.replaceAll("'", "'\"'\"'")}'`;
1301
3824
  }
1302
- function formatLiveDesktopForResult(desktop) {
3825
+ function formatLiveDesktopForResult(desktop, redactRepoNames) {
1303
3826
  return {
3827
+ ...(desktop.completion?.actorStatus ? { actorStatus: desktop.completion.actorStatus } : {}),
3828
+ ...(desktop.completion?.appStatus ? { appStatus: desktop.completion.appStatus } : {}),
1304
3829
  ...(desktop.bootstrap ? { bootstrapStatus: desktop.bootstrap.status } : {}),
1305
3830
  ...(desktop.completion ? { completionReason: desktop.completion.reason, completionStatus: desktop.completion.status } : {}),
1306
3831
  repo: desktop.repo,
1307
3832
  ...(desktop.screenshot ? { screenshotPresent: true } : {}),
1308
- ...(desktop.sandboxId ? { sandboxId: desktop.sandboxId } : {}),
3833
+ ...(!redactRepoNames && desktop.sandboxId ? { sandboxId: desktop.sandboxId } : {}),
1309
3834
  streamId: desktop.streamId,
1310
- urlPresent: Boolean(desktop.url)
3835
+ urlPresent: Boolean(desktop.url),
3836
+ ...(desktop.completion?.visualStatus ? { visualStatus: desktop.completion.visualStatus } : {}),
3837
+ ...(desktop.completion?.visualWindowCount === undefined ? {} : { visualWindowCount: desktop.completion.visualWindowCount })
1311
3838
  };
1312
3839
  }
1313
3840
  function missingLiveKeys(env) {
1314
- return ["E2B_API_KEY", "OPENAI_API_KEY"].filter((name) => !env[name]?.trim());
3841
+ const missing = ["E2B_API_KEY"].filter((name) => !env[name]?.trim());
3842
+ const actorAuthRequested = remoteActorAuthRequested(env);
3843
+ const actorAuthPresent = Boolean(env.CODEX_API_KEY?.trim() || env.CODEX_ACCESS_TOKEN?.trim() || env.OPENAI_API_KEY?.trim());
3844
+ if (actorAuthRequested && !hostCodexActorRequested(env) && !actorAuthPresent) {
3845
+ missing.push(OSS_META_LAB_ACTOR_AUTH_PLACEHOLDER);
3846
+ }
3847
+ return missing;
1315
3848
  }
1316
3849
  function readPositiveInt(value, fallback) {
1317
3850
  if (!value || !/^\d+$/.test(value)) {
@@ -1335,7 +3868,14 @@ async function wait(ms) {
1335
3868
  }
1336
3869
  function compactError(error) {
1337
3870
  const message = error instanceof Error ? error.message : String(error);
1338
- return message.replace(/\s+/g, " ").slice(0, 240);
3871
+ return message
3872
+ .replace(/sk-[A-Za-z0-9_-]{20,}/g, "[redacted-openai-key]")
3873
+ .replace(/e2b_[A-Za-z0-9_-]{12,}/g, "[redacted-e2b-key]")
3874
+ .replace(/gh[pousr]_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
3875
+ .replace(/github_pat_[A-Za-z0-9_]{12,}/g, "[redacted-github-token]")
3876
+ .replace(/https?:\/\/[^/\s]*e2b[^)\s]+/gi, "[redacted-e2b-url]")
3877
+ .replace(/\s+/g, " ")
3878
+ .slice(0, 240);
1339
3879
  }
1340
3880
  function makeMetaRunId() {
1341
3881
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
@@ -1344,6 +3884,19 @@ function makeMetaRunId() {
1344
3884
  function repoSlugToken(repo) {
1345
3885
  return repo.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1346
3886
  }
3887
+ function repoArtifactLabel(assignment) {
3888
+ return `repo-${String(assignment.index).padStart(2, "0")}`;
3889
+ }
3890
+ function redactAssignments(assignments, redactRepoNames) {
3891
+ if (!redactRepoNames) {
3892
+ return assignments;
3893
+ }
3894
+ return assignments.map((assignment) => ({
3895
+ ...assignment,
3896
+ repo: repoArtifactLabel(assignment),
3897
+ scenarioId: `oss-meta-${repoArtifactLabel(assignment)}`
3898
+ }));
3899
+ }
1347
3900
  function safeArtifactToken(value) {
1348
3901
  const token = value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
1349
3902
  return token || "artifact";