mimetic-cli 0.1.4 → 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.
- package/README.md +67 -12
- package/dist/env-file.d.ts +14 -0
- package/dist/env-file.js +108 -0
- package/dist/env-file.js.map +1 -0
- package/dist/feedback.d.ts +7 -5
- package/dist/feedback.js +61 -4
- package/dist/feedback.js.map +1 -1
- package/dist/init-templates.js +29 -0
- package/dist/init-templates.js.map +1 -1
- package/dist/lab-app-runner.d.ts +78 -0
- package/dist/lab-app-runner.js +403 -0
- package/dist/lab-app-runner.js.map +1 -0
- package/dist/labs.d.ts +67 -0
- package/dist/labs.js +257 -0
- package/dist/labs.js.map +1 -0
- package/dist/observer-assets.js +473 -25
- package/dist/observer-assets.js.map +1 -1
- package/dist/observer.d.ts +6 -0
- package/dist/observer.js +49 -8
- package/dist/observer.js.map +1 -1
- package/dist/oss-lab.d.ts +1 -1
- package/dist/oss-lab.js +6 -6
- package/dist/oss-lab.js.map +1 -1
- package/dist/oss-meta-lab.d.ts +113 -1
- package/dist/oss-meta-lab.js +2753 -200
- package/dist/oss-meta-lab.js.map +1 -1
- package/dist/oss-remote-telemetry.d.ts +77 -0
- package/dist/oss-remote-telemetry.js +393 -0
- package/dist/oss-remote-telemetry.js.map +1 -0
- package/dist/program.d.ts +8 -0
- package/dist/program.js +668 -70
- package/dist/program.js.map +1 -1
- package/dist/run.d.ts +105 -3
- package/dist/run.js +684 -22
- package/dist/run.js.map +1 -1
- package/docs/architecture/local-codex-tui-actor.md +9 -6
- package/docs/architecture/oss-lab-poc.md +119 -47
- package/docs/architecture/project-layout.md +40 -6
- package/docs/contracts/feedback.md +15 -12
- package/docs/contracts/policy.md +9 -2
- package/docs/contracts/run-bundle.md +62 -0
- package/docs/contracts/schemas.md +21 -0
- package/docs/goals/current.md +50 -17
- package/docs/product/open-source-install-experience.md +63 -8
- package/docs/ramp/README.md +26 -8
- package/docs/roadmap/world-class-open-source-v0.md +41 -20
- package/package.json +8 -6
- package/skills/mimetic-cli/SKILL.md +89 -4
- package/skills/mimetic-cli/agents/openai.yaml +1 -1
package/dist/oss-meta-lab.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
89
|
-
if (liveRequested && missingKeys.length === 0) {
|
|
855
|
+
if (liveRequested && missingKeys.length === 0 && !repoAccessPreflightBlocked && !hostActorPlanBlocked && !actorAuthPreflightBlocked) {
|
|
90
856
|
try {
|
|
91
|
-
liveDesktops = await launchLiveDesktops(assignments,
|
|
92
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
await
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
165
|
-
await
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 ${
|
|
1199
|
+
? `Headed E2B desktop lane assigned to ${repoLabel}; ${completion.reason}`
|
|
241
1200
|
: liveDesktop?.bootstrap?.status === "started"
|
|
242
|
-
? `Headed E2B desktop lane assigned to ${
|
|
243
|
-
: `Headed E2B desktop lane assigned to ${
|
|
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 - ${
|
|
1211
|
+
label: `E2B desktop - ${repoLabel}`,
|
|
253
1212
|
status,
|
|
254
|
-
transport:
|
|
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" :
|
|
259
|
-
...(
|
|
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 - ${
|
|
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/${
|
|
275
|
-
intent: "Watch the headed desktop where
|
|
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
|
-
?
|
|
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
|
-
:
|
|
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 ${
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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: `${
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
1511
|
+
return `Bootstrap launcher failed for ${repoLabel}.`;
|
|
515
1512
|
}
|
|
516
1513
|
if (liveDesktop?.url) {
|
|
517
|
-
return `Live E2B desktop connected for ${
|
|
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 ${
|
|
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 ${
|
|
1520
|
+
return `Waiting for ${args.missingKeys.join(", ")} before launching ${repoLabel}.`;
|
|
524
1521
|
}
|
|
525
|
-
return `Ready to launch E2B desktop and inject Codex TUI for ${
|
|
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
|
-
|
|
533
|
-
? "
|
|
534
|
-
:
|
|
535
|
-
? "
|
|
536
|
-
:
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
676
|
-
"5.
|
|
677
|
-
"6.
|
|
678
|
-
"7.
|
|
679
|
-
"8.
|
|
680
|
-
"9.
|
|
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
|
-
|
|
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
|
-
|
|
725
|
-
|
|
726
|
-
repo: assignment.repo,
|
|
1941
|
+
...OSS_META_LAB_PROVIDER_METADATA,
|
|
1942
|
+
repo: repoLabel,
|
|
727
1943
|
simId: assignment.simId
|
|
728
1944
|
},
|
|
729
1945
|
envs: {
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
|
|
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} ${
|
|
2577
|
+
const title = `Mimetic ${assignment.index} ${display.repoLabel}`;
|
|
957
2578
|
const baseTail = [
|
|
958
|
-
`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
|
|
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
|
|
1079
|
-
? fs.readFileSync(
|
|
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
|
|
1082
|
-
.replace(/sk-[A-Za-z0-9_-]{
|
|
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" -
|
|
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.
|
|
2997
|
+
echo "repo=${args.displayRepo}"
|
|
1112
2998
|
echo "public_safe=1"
|
|
1113
|
-
echo "
|
|
1114
|
-
echo "
|
|
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
|
-
|
|
1150
|
-
local
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
-
|
|
3590
|
+
ACTOR_STATUS=failed
|
|
1155
3591
|
fi
|
|
1156
|
-
echo "
|
|
1157
|
-
|
|
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
|
-
|
|
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 "==
|
|
1171
|
-
if
|
|
1172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
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
|
|
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";
|