mimetic-cli 0.1.1 → 0.1.3
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/AGENTS.md +66 -0
- package/CONTRIBUTING.md +39 -0
- package/README.md +5 -0
- package/SECURITY.md +34 -0
- package/dist/core/git-state.d.ts +31 -0
- package/dist/core/git-state.js +142 -0
- package/dist/core/git-state.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/run-primitives.d.ts +66 -0
- package/dist/core/run-primitives.js +120 -0
- package/dist/core/run-primitives.js.map +1 -0
- package/dist/observer-assets.js +1663 -2180
- package/dist/observer-assets.js.map +1 -1
- package/dist/observer-data.d.ts +1 -1
- package/dist/observer-data.js +5 -1
- package/dist/observer-data.js.map +1 -1
- package/dist/observer.js +8 -61
- package/dist/observer.js.map +1 -1
- package/dist/oss-meta-lab.d.ts +50 -0
- package/dist/oss-meta-lab.js +454 -27
- package/dist/oss-meta-lab.js.map +1 -1
- package/dist/program.d.ts +6 -0
- package/dist/program.js +75 -8
- package/dist/program.js.map +1 -1
- package/dist/run.d.ts +19 -6
- package/dist/run.js +1263 -9
- package/dist/run.js.map +1 -1
- package/docs/architecture/github-feedback-loop.md +189 -0
- package/docs/architecture/local-codex-tui-actor.md +210 -0
- package/docs/architecture/observer.md +109 -0
- package/docs/architecture/oss-lab-poc.md +170 -0
- package/docs/architecture/project-layout.md +132 -0
- package/docs/assets/mimetic-oss-lab-observer.png +0 -0
- package/docs/contracts/adapter-fixtures.md +80 -0
- package/docs/contracts/core.md +71 -0
- package/docs/contracts/feedback.md +131 -0
- package/docs/contracts/policy.md +273 -0
- package/docs/contracts/run-bundle.md +110 -0
- package/docs/contracts/schemas.md +511 -0
- package/docs/goals/current.md +163 -0
- package/docs/principles/self-driving-harness.md +129 -0
- package/docs/product/open-source-install-experience.md +138 -0
- package/docs/ramp/README.md +167 -0
- package/docs/release/open-source-readiness.md +171 -0
- package/docs/release/public-readiness-standard.md +205 -0
- package/docs/roadmap/world-class-open-source-v0.md +286 -0
- package/package.json +14 -2
- package/skills/mimetic-cli/SKILL.md +1 -1
package/dist/run.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
3
5
|
import path from "node:path";
|
|
6
|
+
import { buildObserverData } from "./observer-data.js";
|
|
4
7
|
export const RUN_BUNDLE_SCHEMA = "mimetic.run-bundle.v1";
|
|
5
8
|
export const REVIEW_SCHEMA = "mimetic.review.v1";
|
|
6
9
|
export const VERIFY_SCHEMA = "mimetic.verify-result.v1";
|
|
@@ -11,6 +14,9 @@ const sensitivePatterns = [
|
|
|
11
14
|
/gho_[a-z0-9_]{12,}/i,
|
|
12
15
|
/BEGIN (RSA|OPENSSH|PRIVATE) KEY/i
|
|
13
16
|
];
|
|
17
|
+
const LOCAL_CODEX_TUI_DEFAULT_TIMEOUT_MS = 240_000;
|
|
18
|
+
const LOCAL_CODEX_TUI_MAX_TIMEOUT_MS = 600_000;
|
|
19
|
+
const LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS = 80_000;
|
|
14
20
|
const builtinPersona = {
|
|
15
21
|
id: "builtin-synthetic-new-user",
|
|
16
22
|
name: "Built-in Synthetic New User",
|
|
@@ -37,15 +43,15 @@ export async function runDryRun(options) {
|
|
|
37
43
|
error: cwdError
|
|
38
44
|
};
|
|
39
45
|
}
|
|
40
|
-
if (!options.
|
|
46
|
+
if (options.actor !== undefined && !isLocalCodexActor(options.actor)) {
|
|
41
47
|
return {
|
|
42
48
|
schema: "mimetic.run-result.v1",
|
|
43
49
|
ok: false,
|
|
44
50
|
cwd,
|
|
45
51
|
warnings,
|
|
46
52
|
error: {
|
|
47
|
-
code: "
|
|
48
|
-
message:
|
|
53
|
+
code: "MIMETIC_UNSUPPORTED_ACTOR",
|
|
54
|
+
message: `Unsupported actor: ${options.actor}`
|
|
49
55
|
}
|
|
50
56
|
};
|
|
51
57
|
}
|
|
@@ -62,6 +68,25 @@ export async function runDryRun(options) {
|
|
|
62
68
|
}
|
|
63
69
|
};
|
|
64
70
|
}
|
|
71
|
+
if (!options.dryRun) {
|
|
72
|
+
const actor = resolveRequestedLocalCodexActor(options.actor);
|
|
73
|
+
if (actor === "codex-tui") {
|
|
74
|
+
return runLocalCodexTui({ ...options, actor, cwd, simCount });
|
|
75
|
+
}
|
|
76
|
+
if (actor === "codex-exec") {
|
|
77
|
+
return runLocalCodexExec({ ...options, actor, cwd, simCount });
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
schema: "mimetic.run-result.v1",
|
|
81
|
+
ok: false,
|
|
82
|
+
cwd,
|
|
83
|
+
warnings,
|
|
84
|
+
error: {
|
|
85
|
+
code: "MIMETIC_LIVE_RUN_UNIMPLEMENTED",
|
|
86
|
+
message: "Only run --dry-run is implemented unless --actor codex-tui, --actor codex-exec, or the matching MIMETIC_ENABLE_LOCAL_CODEX_* env var is set."
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
65
90
|
const now = new Date();
|
|
66
91
|
const createdAt = now.toISOString();
|
|
67
92
|
const runId = options.runId ?? `dryrun-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
@@ -162,14 +187,714 @@ export async function runDryRun(options) {
|
|
|
162
187
|
warnings
|
|
163
188
|
};
|
|
164
189
|
}
|
|
190
|
+
function isLocalCodexActor(value) {
|
|
191
|
+
return value === "codex-tui" || value === "codex-exec";
|
|
192
|
+
}
|
|
193
|
+
function resolveRequestedLocalCodexActor(actor) {
|
|
194
|
+
if (actor && isLocalCodexActor(actor)) {
|
|
195
|
+
return actor;
|
|
196
|
+
}
|
|
197
|
+
if (process.env.MIMETIC_ENABLE_LOCAL_CODEX_TUI === "1") {
|
|
198
|
+
return "codex-tui";
|
|
199
|
+
}
|
|
200
|
+
if (process.env.MIMETIC_ENABLE_LOCAL_CODEX_EXEC === "1") {
|
|
201
|
+
return "codex-exec";
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
async function runLocalCodexTui(options) {
|
|
206
|
+
const warnings = [];
|
|
207
|
+
if (options.simCount !== 1) {
|
|
208
|
+
return {
|
|
209
|
+
schema: "mimetic.run-result.v1",
|
|
210
|
+
ok: false,
|
|
211
|
+
cwd: options.cwd,
|
|
212
|
+
warnings,
|
|
213
|
+
error: {
|
|
214
|
+
code: "MIMETIC_ACTOR_FANOUT_UNIMPLEMENTED",
|
|
215
|
+
message: "Local Codex TUI actor support is intentionally limited to --sims 1 in this slice. Split 4x fanout after the 1x lifecycle is deterministic."
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const timeoutMs = normalizeActorTimeout(options.timeoutMs ?? readEnvInteger("MIMETIC_CODEX_ACTOR_TIMEOUT_MS") ?? LOCAL_CODEX_TUI_DEFAULT_TIMEOUT_MS);
|
|
220
|
+
if (timeoutMs === null) {
|
|
221
|
+
return {
|
|
222
|
+
schema: "mimetic.run-result.v1",
|
|
223
|
+
ok: false,
|
|
224
|
+
cwd: options.cwd,
|
|
225
|
+
warnings,
|
|
226
|
+
error: {
|
|
227
|
+
code: "MIMETIC_INVALID_TIMEOUT",
|
|
228
|
+
message: `--timeout-ms must be an integer between 1 and ${LOCAL_CODEX_TUI_MAX_TIMEOUT_MS}.`
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const now = new Date();
|
|
233
|
+
const createdAt = now.toISOString();
|
|
234
|
+
const runId = options.runId ?? `codex-tui-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
235
|
+
const artifactRoot = path.join(".mimetic", "runs", runId);
|
|
236
|
+
const absoluteArtifactRoot = path.join(options.cwd, artifactRoot);
|
|
237
|
+
const transcriptPath = path.join(absoluteArtifactRoot, "transcripts", "codex-tui-sanitized.txt");
|
|
238
|
+
const actorTracePath = path.join(absoluteArtifactRoot, "actor.json");
|
|
239
|
+
const eventsPath = path.join(absoluteArtifactRoot, "events.ndjson");
|
|
240
|
+
const packageName = await readPackageName(options.cwd);
|
|
241
|
+
const mimeticSource = await directoryExists(path.join(options.cwd, "mimetic")) ? "present" : "missing";
|
|
242
|
+
const selection = await loadDryRunSelection(options.cwd, mimeticSource);
|
|
243
|
+
if (mimeticSource === "missing") {
|
|
244
|
+
warnings.push("Committed mimetic/ source was not found; using built-in synthetic local actor defaults.");
|
|
245
|
+
}
|
|
246
|
+
warnings.push(...selection.warnings);
|
|
247
|
+
const verdictNonce = randomUUID().slice(0, 12);
|
|
248
|
+
const prompt = buildLocalCodexTuiPrompt(selection, verdictNonce);
|
|
249
|
+
const promptDigest = digestText(prompt);
|
|
250
|
+
const command = resolveLocalCodexTuiCommand(options.cwd, prompt, options.actorCommand);
|
|
251
|
+
const usesDefaultCodexCommand = options.actorCommand === undefined && process.env.MIMETIC_CODEX_ACTOR_COMMAND === undefined;
|
|
252
|
+
const simId = "sim-01";
|
|
253
|
+
const streamId = "sim-01-codex-tui";
|
|
254
|
+
const events = [];
|
|
255
|
+
const appendEvent = async (type, message, level = "info") => {
|
|
256
|
+
events.push({
|
|
257
|
+
id: `event-${String(events.length + 1).padStart(3, "0")}`,
|
|
258
|
+
at: new Date().toISOString(),
|
|
259
|
+
level,
|
|
260
|
+
type,
|
|
261
|
+
message,
|
|
262
|
+
simId,
|
|
263
|
+
streamId
|
|
264
|
+
});
|
|
265
|
+
await writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
|
|
266
|
+
};
|
|
267
|
+
await mkdir(path.dirname(transcriptPath), { recursive: true });
|
|
268
|
+
let actor;
|
|
269
|
+
const trustPreflight = usesDefaultCodexCommand ? await checkCodexWorkspaceTrust(options.cwd) : { ok: true };
|
|
270
|
+
if (!trustPreflight.ok) {
|
|
271
|
+
actor = {
|
|
272
|
+
durationMs: 0,
|
|
273
|
+
reason: trustPreflight.message,
|
|
274
|
+
status: "blocked",
|
|
275
|
+
transcript: `${trustPreflight.message}\nRecovery: ${trustPreflight.recoveryCommand}\n`,
|
|
276
|
+
transcriptBytes: Buffer.byteLength(trustPreflight.message)
|
|
277
|
+
};
|
|
278
|
+
await appendEvent("actor.preflight.blocked", trustPreflight.message, "warn");
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
await appendEvent("actor.spawned", `Spawned local Codex TUI actor command ${command.name} in explicit opt-in mode.`);
|
|
282
|
+
await appendEvent("actor.prompt.submitted", `Submitted bounded public-safe dogfood prompt digest ${promptDigest}; raw prompt omitted from event log.`);
|
|
283
|
+
await appendEvent("actor.running", "Published local Codex TUI running snapshot for Observer polling.");
|
|
284
|
+
const runningAt = new Date().toISOString();
|
|
285
|
+
const runningBundle = {
|
|
286
|
+
schema: RUN_BUNDLE_SCHEMA,
|
|
287
|
+
runId,
|
|
288
|
+
mode: "live",
|
|
289
|
+
simCount: 1,
|
|
290
|
+
createdAt,
|
|
291
|
+
cwd: options.cwd,
|
|
292
|
+
artifactRoot,
|
|
293
|
+
source: {
|
|
294
|
+
packageName,
|
|
295
|
+
mimeticSource,
|
|
296
|
+
git: {
|
|
297
|
+
status: "not_captured",
|
|
298
|
+
note: "Source git state capture is planned for the core primitives slice."
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
persona: selection.persona,
|
|
302
|
+
scenario: selection.scenario,
|
|
303
|
+
lifecycle: [
|
|
304
|
+
{
|
|
305
|
+
at: createdAt,
|
|
306
|
+
event: "run.created",
|
|
307
|
+
message: "Live local Codex TUI run created with one explicit opt-in actor."
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
at: createdAt,
|
|
311
|
+
event: "actor.selected",
|
|
312
|
+
message: "Selected local codex-tui actor."
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
at: runningAt,
|
|
316
|
+
event: "actor.running",
|
|
317
|
+
message: "Local Codex TUI actor is running; Observer data will refresh with sanitized evidence after completion."
|
|
318
|
+
}
|
|
319
|
+
],
|
|
320
|
+
simulations: [
|
|
321
|
+
{
|
|
322
|
+
id: simId,
|
|
323
|
+
index: 1,
|
|
324
|
+
personaId: selection.persona.id,
|
|
325
|
+
scenarioId: selection.scenario.id,
|
|
326
|
+
status: "running",
|
|
327
|
+
streamKind: "tui",
|
|
328
|
+
mode: "tui-sim",
|
|
329
|
+
progress: 35,
|
|
330
|
+
currentStep: "Local Codex TUI actor running",
|
|
331
|
+
summary: "Local Codex TUI actor is running.",
|
|
332
|
+
streamIds: [streamId],
|
|
333
|
+
startedAt: createdAt,
|
|
334
|
+
updatedAt: runningAt
|
|
335
|
+
}
|
|
336
|
+
],
|
|
337
|
+
streams: [
|
|
338
|
+
{
|
|
339
|
+
id: streamId,
|
|
340
|
+
simId,
|
|
341
|
+
kind: "tui",
|
|
342
|
+
label: "Local Codex TUI actor",
|
|
343
|
+
status: "running",
|
|
344
|
+
transport: "pty",
|
|
345
|
+
updatedAt: runningAt,
|
|
346
|
+
embed: {
|
|
347
|
+
kind: "terminal",
|
|
348
|
+
title: "Local Codex TUI actor"
|
|
349
|
+
},
|
|
350
|
+
terminal: {
|
|
351
|
+
title: "Local Codex TUI actor",
|
|
352
|
+
format: "ansi",
|
|
353
|
+
stdin: "sent",
|
|
354
|
+
tail: "Codex TUI actor is running; sanitized transcript evidence will be linked after completion."
|
|
355
|
+
},
|
|
356
|
+
completion: {
|
|
357
|
+
checkedAt: runningAt,
|
|
358
|
+
reason: "actor process is still running",
|
|
359
|
+
status: "running"
|
|
360
|
+
},
|
|
361
|
+
artifacts: [
|
|
362
|
+
{ label: "run bundle", path: "run.json", kind: "bundle" },
|
|
363
|
+
{ label: "review", path: "review.md", kind: "review" },
|
|
364
|
+
{ label: "event log", path: "events.ndjson", kind: "events" }
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
events,
|
|
369
|
+
redaction: {
|
|
370
|
+
status: "passed",
|
|
371
|
+
notes: "Running TUI bundle contains no raw transcript yet; final actor output will be redacted before persistence."
|
|
372
|
+
},
|
|
373
|
+
artifacts: {
|
|
374
|
+
run: "run.json",
|
|
375
|
+
reviewJson: "review.json",
|
|
376
|
+
reviewMarkdown: "review.md",
|
|
377
|
+
observerData: "observer/observer-data.json",
|
|
378
|
+
events: "events.ndjson"
|
|
379
|
+
},
|
|
380
|
+
review: createLocalActorRunningReviewSummary("Codex TUI"),
|
|
381
|
+
feedbackCandidates: []
|
|
382
|
+
};
|
|
383
|
+
await writeRunBundleArtifacts(absoluteArtifactRoot, runningBundle);
|
|
384
|
+
await writeJson(path.join(options.cwd, ".mimetic", "runs", "latest.json"), {
|
|
385
|
+
schema: "mimetic.latest-run.v1",
|
|
386
|
+
runId,
|
|
387
|
+
path: artifactRoot,
|
|
388
|
+
updatedAt: runningAt
|
|
389
|
+
});
|
|
390
|
+
actor = await executeLocalActorCommand(command, {
|
|
391
|
+
cwd: options.cwd,
|
|
392
|
+
timeoutMs,
|
|
393
|
+
verdictNonce
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
const completedAt = new Date().toISOString();
|
|
397
|
+
const redactedTranscript = redactSensitiveText(actor.transcript);
|
|
398
|
+
const tail = tailText(redactedTranscript, 6_000);
|
|
399
|
+
const status = actor.status;
|
|
400
|
+
const verdictReason = actor.reason;
|
|
401
|
+
await writeFile(transcriptPath, redactedTranscript.length > 0 ? redactedTranscript : "No transcript output captured.\n", "utf8");
|
|
402
|
+
await writeJson(actorTracePath, {
|
|
403
|
+
schema: "mimetic.local-codex-tui-actor.v1",
|
|
404
|
+
actor: "codex-tui",
|
|
405
|
+
commandName: command.name,
|
|
406
|
+
promptDigest,
|
|
407
|
+
verdictNonce,
|
|
408
|
+
startedAt: createdAt,
|
|
409
|
+
completedAt,
|
|
410
|
+
durationMs: actor.durationMs,
|
|
411
|
+
exitCode: actor.exitCode,
|
|
412
|
+
signal: actor.signal,
|
|
413
|
+
status,
|
|
414
|
+
timeoutMs,
|
|
415
|
+
transcriptBytes: actor.transcriptBytes,
|
|
416
|
+
transcriptPath: "transcripts/codex-tui-sanitized.txt",
|
|
417
|
+
redaction: "passed"
|
|
418
|
+
});
|
|
419
|
+
await appendEvent("actor.observation", `Captured ${actor.transcriptBytes} output byte${actor.transcriptBytes === 1 ? "" : "s"}; sanitized transcript tail recorded with redaction=passed.`);
|
|
420
|
+
await appendEvent("actor.artifact", "Wrote sanitized Codex TUI transcript and actor trace artifacts under the ignored run directory.");
|
|
421
|
+
await appendEvent("actor.verdict", `Local Codex TUI actor verdict ${status}: ${verdictReason}`, status === "passed" ? "info" : status === "timed_out" ? "warn" : "error");
|
|
422
|
+
await appendEvent(status === "timed_out" ? "actor.timeout" : status === "blocked" && !trustPreflight.ok ? "actor.blocked" : "actor.exited", status === "timed_out"
|
|
423
|
+
? `Actor timed out after ${timeoutMs}ms; last safe observation retained.`
|
|
424
|
+
: status === "blocked" && !trustPreflight.ok
|
|
425
|
+
? "Actor launch was blocked by preflight before spawn."
|
|
426
|
+
: `Actor exited with code ${actor.exitCode ?? "null"}${actor.signal ? ` and signal ${actor.signal}` : ""}.`, status === "passed" ? "info" : "warn");
|
|
427
|
+
const bundle = {
|
|
428
|
+
schema: RUN_BUNDLE_SCHEMA,
|
|
429
|
+
runId,
|
|
430
|
+
mode: "live",
|
|
431
|
+
simCount: 1,
|
|
432
|
+
createdAt,
|
|
433
|
+
cwd: options.cwd,
|
|
434
|
+
artifactRoot,
|
|
435
|
+
source: {
|
|
436
|
+
packageName,
|
|
437
|
+
mimeticSource,
|
|
438
|
+
git: {
|
|
439
|
+
status: "not_captured",
|
|
440
|
+
note: "Source git state capture is planned for the core primitives slice."
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
persona: selection.persona,
|
|
444
|
+
scenario: selection.scenario,
|
|
445
|
+
lifecycle: [
|
|
446
|
+
{
|
|
447
|
+
at: createdAt,
|
|
448
|
+
event: "run.created",
|
|
449
|
+
message: "Live local Codex TUI run created with one explicit opt-in actor."
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
at: createdAt,
|
|
453
|
+
event: "actor.selected",
|
|
454
|
+
message: "Selected local codex-tui actor."
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
at: completedAt,
|
|
458
|
+
event: "review.skeleton.created",
|
|
459
|
+
message: "Created review skeleton from sanitized actor lifecycle evidence."
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
simulations: [
|
|
463
|
+
{
|
|
464
|
+
id: simId,
|
|
465
|
+
index: 1,
|
|
466
|
+
personaId: selection.persona.id,
|
|
467
|
+
scenarioId: selection.scenario.id,
|
|
468
|
+
status,
|
|
469
|
+
streamKind: "tui",
|
|
470
|
+
mode: "tui-sim",
|
|
471
|
+
progress: 100,
|
|
472
|
+
currentStep: status === "passed" ? "Local Codex TUI actor completed" : "Local Codex TUI actor needs review",
|
|
473
|
+
summary: `Local Codex TUI actor ${status}: ${verdictReason}`,
|
|
474
|
+
streamIds: [streamId],
|
|
475
|
+
startedAt: createdAt,
|
|
476
|
+
updatedAt: completedAt
|
|
477
|
+
}
|
|
478
|
+
],
|
|
479
|
+
streams: [
|
|
480
|
+
{
|
|
481
|
+
id: streamId,
|
|
482
|
+
simId,
|
|
483
|
+
kind: "tui",
|
|
484
|
+
label: "Local Codex TUI actor",
|
|
485
|
+
status,
|
|
486
|
+
transport: "pty",
|
|
487
|
+
updatedAt: completedAt,
|
|
488
|
+
embed: {
|
|
489
|
+
kind: "terminal",
|
|
490
|
+
title: "Local Codex TUI actor"
|
|
491
|
+
},
|
|
492
|
+
terminal: {
|
|
493
|
+
title: "Local Codex TUI actor",
|
|
494
|
+
format: "ansi",
|
|
495
|
+
stdin: "sent",
|
|
496
|
+
tail
|
|
497
|
+
},
|
|
498
|
+
completion: {
|
|
499
|
+
checkedAt: completedAt,
|
|
500
|
+
...(actor.exitCode === undefined ? {} : { exitCode: actor.exitCode }),
|
|
501
|
+
logTail: tail,
|
|
502
|
+
reason: verdictReason,
|
|
503
|
+
status
|
|
504
|
+
},
|
|
505
|
+
artifacts: [
|
|
506
|
+
{ label: "run bundle", path: "run.json", kind: "bundle" },
|
|
507
|
+
{ label: "review", path: "review.md", kind: "review" },
|
|
508
|
+
{ label: "event log", path: "events.ndjson", kind: "events" },
|
|
509
|
+
{ label: "sanitized transcript", path: "transcripts/codex-tui-sanitized.txt", kind: "log" },
|
|
510
|
+
{ label: "actor trace", path: "actor.json", kind: "trace" }
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
],
|
|
514
|
+
events,
|
|
515
|
+
redaction: {
|
|
516
|
+
status: "passed",
|
|
517
|
+
notes: "Actor output was redacted before transcript and bundle persistence; raw prompt is omitted from event log."
|
|
518
|
+
},
|
|
519
|
+
artifacts: {
|
|
520
|
+
run: "run.json",
|
|
521
|
+
reviewJson: "review.json",
|
|
522
|
+
reviewMarkdown: "review.md",
|
|
523
|
+
observerData: "observer/observer-data.json",
|
|
524
|
+
events: "events.ndjson"
|
|
525
|
+
},
|
|
526
|
+
review: createLocalActorReviewSummary("Codex TUI", status, verdictReason),
|
|
527
|
+
feedbackCandidates: []
|
|
528
|
+
};
|
|
529
|
+
await writeRunBundleArtifacts(absoluteArtifactRoot, bundle);
|
|
530
|
+
await writeJson(path.join(options.cwd, ".mimetic", "runs", "latest.json"), {
|
|
531
|
+
schema: "mimetic.latest-run.v1",
|
|
532
|
+
runId,
|
|
533
|
+
path: artifactRoot,
|
|
534
|
+
updatedAt: completedAt
|
|
535
|
+
});
|
|
536
|
+
return {
|
|
537
|
+
schema: "mimetic.run-result.v1",
|
|
538
|
+
ok: status === "passed",
|
|
539
|
+
runId,
|
|
540
|
+
mode: "live",
|
|
541
|
+
simCount: 1,
|
|
542
|
+
cwd: options.cwd,
|
|
543
|
+
artifactRoot,
|
|
544
|
+
bundlePath: path.join(artifactRoot, "run.json"),
|
|
545
|
+
reviewPath: path.join(artifactRoot, "review.md"),
|
|
546
|
+
latestPath: path.join(".mimetic", "runs", "latest.json"),
|
|
547
|
+
warnings,
|
|
548
|
+
...(status === "passed"
|
|
549
|
+
? {}
|
|
550
|
+
: {
|
|
551
|
+
error: {
|
|
552
|
+
code: "MIMETIC_LOCAL_CODEX_TUI_FAILED",
|
|
553
|
+
message: `Local Codex TUI actor ${status}: ${verdictReason}`
|
|
554
|
+
}
|
|
555
|
+
})
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function buildLocalCodexExecBundle(args) {
|
|
559
|
+
return {
|
|
560
|
+
schema: RUN_BUNDLE_SCHEMA,
|
|
561
|
+
runId: args.runId,
|
|
562
|
+
mode: "live",
|
|
563
|
+
simCount: args.simCount,
|
|
564
|
+
createdAt: args.createdAt,
|
|
565
|
+
cwd: args.cwd,
|
|
566
|
+
artifactRoot: args.artifactRoot,
|
|
567
|
+
source: {
|
|
568
|
+
packageName: args.packageName,
|
|
569
|
+
mimeticSource: args.mimeticSource,
|
|
570
|
+
git: {
|
|
571
|
+
status: "not_captured",
|
|
572
|
+
note: "Source git state capture is planned for the core primitives slice."
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
persona: args.persona,
|
|
576
|
+
scenario: args.scenario,
|
|
577
|
+
lifecycle: args.lifecycle,
|
|
578
|
+
simulations: args.lanes.map((lane, index) => ({
|
|
579
|
+
id: lane.simId,
|
|
580
|
+
index: index + 1,
|
|
581
|
+
personaId: `codex-exec-${lane.focus.id}`,
|
|
582
|
+
scenarioId: args.scenario.id,
|
|
583
|
+
status: lane.status,
|
|
584
|
+
streamKind: "terminal",
|
|
585
|
+
mode: "cli-sim",
|
|
586
|
+
progress: lane.progress,
|
|
587
|
+
currentStep: lane.currentStep,
|
|
588
|
+
summary: lane.summary,
|
|
589
|
+
streamIds: [lane.streamId],
|
|
590
|
+
startedAt: args.createdAt,
|
|
591
|
+
updatedAt: lane.updatedAt
|
|
592
|
+
})),
|
|
593
|
+
streams: args.lanes.map((lane) => {
|
|
594
|
+
const artifacts = [
|
|
595
|
+
{ label: "run bundle", path: "run.json", kind: "bundle" },
|
|
596
|
+
{ label: "review", path: "review.md", kind: "review" },
|
|
597
|
+
{ label: "event log", path: "events.ndjson", kind: "events" },
|
|
598
|
+
...(lane.transcriptPath ? [{ label: "sanitized transcript", path: lane.transcriptPath, kind: "log" }] : []),
|
|
599
|
+
...(lane.tracePath ? [{ label: "actor trace", path: lane.tracePath, kind: "trace" }] : [])
|
|
600
|
+
];
|
|
601
|
+
return {
|
|
602
|
+
id: lane.streamId,
|
|
603
|
+
simId: lane.simId,
|
|
604
|
+
kind: "terminal",
|
|
605
|
+
label: `Local Codex exec - ${lane.focus.label}`,
|
|
606
|
+
status: lane.status,
|
|
607
|
+
transport: "snapshot",
|
|
608
|
+
updatedAt: lane.updatedAt,
|
|
609
|
+
embed: {
|
|
610
|
+
kind: "terminal",
|
|
611
|
+
title: `Local Codex exec - ${lane.focus.label}`
|
|
612
|
+
},
|
|
613
|
+
terminal: {
|
|
614
|
+
title: `Local Codex exec - ${lane.focus.label}`,
|
|
615
|
+
format: "plain",
|
|
616
|
+
stdin: "sent",
|
|
617
|
+
tail: lane.terminalTail
|
|
618
|
+
},
|
|
619
|
+
...(lane.completion ? { completion: lane.completion } : {}),
|
|
620
|
+
artifacts
|
|
621
|
+
};
|
|
622
|
+
}),
|
|
623
|
+
events: args.events,
|
|
624
|
+
redaction: {
|
|
625
|
+
status: "passed",
|
|
626
|
+
notes: "Actor output was redacted before transcript and bundle persistence; raw prompt is omitted from event log."
|
|
627
|
+
},
|
|
628
|
+
artifacts: {
|
|
629
|
+
run: "run.json",
|
|
630
|
+
reviewJson: "review.json",
|
|
631
|
+
reviewMarkdown: "review.md",
|
|
632
|
+
observerData: "observer/observer-data.json",
|
|
633
|
+
events: "events.ndjson"
|
|
634
|
+
},
|
|
635
|
+
review: args.review,
|
|
636
|
+
feedbackCandidates: []
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
async function runLocalCodexExec(options) {
|
|
640
|
+
const warnings = [];
|
|
641
|
+
if (options.simCount > 4) {
|
|
642
|
+
return {
|
|
643
|
+
schema: "mimetic.run-result.v1",
|
|
644
|
+
ok: false,
|
|
645
|
+
cwd: options.cwd,
|
|
646
|
+
warnings,
|
|
647
|
+
error: {
|
|
648
|
+
code: "MIMETIC_ACTOR_FANOUT_UNIMPLEMENTED",
|
|
649
|
+
message: "Local Codex exec actor fanout is intentionally limited to --sims 4 in this slice."
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
const timeoutMs = normalizeActorTimeout(options.timeoutMs ?? readEnvInteger("MIMETIC_CODEX_ACTOR_TIMEOUT_MS") ?? LOCAL_CODEX_TUI_DEFAULT_TIMEOUT_MS);
|
|
654
|
+
if (timeoutMs === null) {
|
|
655
|
+
return {
|
|
656
|
+
schema: "mimetic.run-result.v1",
|
|
657
|
+
ok: false,
|
|
658
|
+
cwd: options.cwd,
|
|
659
|
+
warnings,
|
|
660
|
+
error: {
|
|
661
|
+
code: "MIMETIC_INVALID_TIMEOUT",
|
|
662
|
+
message: `--timeout-ms must be an integer between 1 and ${LOCAL_CODEX_TUI_MAX_TIMEOUT_MS}.`
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
const now = new Date();
|
|
667
|
+
const createdAt = now.toISOString();
|
|
668
|
+
const runId = options.runId ?? `codex-exec-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
669
|
+
const artifactRoot = path.join(".mimetic", "runs", runId);
|
|
670
|
+
const absoluteArtifactRoot = path.join(options.cwd, artifactRoot);
|
|
671
|
+
const eventsPath = path.join(absoluteArtifactRoot, "events.ndjson");
|
|
672
|
+
const packageName = await readPackageName(options.cwd);
|
|
673
|
+
const mimeticSource = await directoryExists(path.join(options.cwd, "mimetic")) ? "present" : "missing";
|
|
674
|
+
const selection = await loadDryRunSelection(options.cwd, mimeticSource);
|
|
675
|
+
if (mimeticSource === "missing") {
|
|
676
|
+
warnings.push("Committed mimetic/ source was not found; using built-in synthetic local actor defaults.");
|
|
677
|
+
}
|
|
678
|
+
warnings.push(...selection.warnings);
|
|
679
|
+
const events = [];
|
|
680
|
+
const pushEvent = (type, message, level = "info", simId, streamId) => {
|
|
681
|
+
events.push({
|
|
682
|
+
id: `event-${String(events.length + 1).padStart(3, "0")}`,
|
|
683
|
+
at: new Date().toISOString(),
|
|
684
|
+
level,
|
|
685
|
+
type,
|
|
686
|
+
message,
|
|
687
|
+
...(simId === undefined ? {} : { simId }),
|
|
688
|
+
...(streamId === undefined ? {} : { streamId })
|
|
689
|
+
});
|
|
690
|
+
};
|
|
691
|
+
await mkdir(path.join(absoluteArtifactRoot, "transcripts"), { recursive: true });
|
|
692
|
+
await mkdir(path.join(absoluteArtifactRoot, "actors"), { recursive: true });
|
|
693
|
+
await mkdir(path.join(absoluteArtifactRoot, "observer"), { recursive: true });
|
|
694
|
+
await writeJson(path.join(options.cwd, ".mimetic", "runs", "latest.json"), {
|
|
695
|
+
schema: "mimetic.latest-run.v1",
|
|
696
|
+
runId,
|
|
697
|
+
path: artifactRoot,
|
|
698
|
+
updatedAt: createdAt
|
|
699
|
+
});
|
|
700
|
+
const lanes = Array.from({ length: options.simCount }, (_, index) => {
|
|
701
|
+
const focus = localCodexExecFocus(index);
|
|
702
|
+
const simId = `sim-${String(index + 1).padStart(2, "0")}`;
|
|
703
|
+
const streamId = `${simId}-codex-exec`;
|
|
704
|
+
const prompt = buildLocalCodexExecPrompt(selection, {
|
|
705
|
+
focus,
|
|
706
|
+
index: index + 1,
|
|
707
|
+
total: options.simCount
|
|
708
|
+
});
|
|
709
|
+
const promptDigest = digestText(prompt);
|
|
710
|
+
const command = resolveLocalCodexExecCommand(options.cwd, prompt, options.actorCommand);
|
|
711
|
+
pushEvent("actor.spawned", `Spawned local Codex exec actor lane ${index + 1}/${options.simCount} (${focus.label}) command ${command.name} in explicit opt-in mode.`, "info", simId, streamId);
|
|
712
|
+
pushEvent("actor.prompt.submitted", `Submitted bounded public-safe dogfood prompt digest ${promptDigest}; raw prompt omitted from event log.`, "info", simId, streamId);
|
|
713
|
+
return { command, focus, promptDigest, simId, streamId };
|
|
714
|
+
});
|
|
715
|
+
for (const lane of lanes) {
|
|
716
|
+
pushEvent("actor.running", "Published local Codex exec running snapshot for Observer polling.", "info", lane.simId, lane.streamId);
|
|
717
|
+
}
|
|
718
|
+
await writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
|
|
719
|
+
const baseLifecycle = [
|
|
720
|
+
{
|
|
721
|
+
at: createdAt,
|
|
722
|
+
event: "run.created",
|
|
723
|
+
message: `Live local Codex exec run created with ${options.simCount} explicit opt-in actor${options.simCount === 1 ? "" : "s"}.`
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
at: createdAt,
|
|
727
|
+
event: "actor.selected",
|
|
728
|
+
message: "Selected local codex-exec actor."
|
|
729
|
+
}
|
|
730
|
+
];
|
|
731
|
+
const runningAt = new Date().toISOString();
|
|
732
|
+
const runningBundle = buildLocalCodexExecBundle({
|
|
733
|
+
runId,
|
|
734
|
+
simCount: options.simCount,
|
|
735
|
+
createdAt,
|
|
736
|
+
cwd: options.cwd,
|
|
737
|
+
artifactRoot,
|
|
738
|
+
packageName,
|
|
739
|
+
mimeticSource,
|
|
740
|
+
persona: selection.persona,
|
|
741
|
+
scenario: selection.scenario,
|
|
742
|
+
lifecycle: [
|
|
743
|
+
...baseLifecycle,
|
|
744
|
+
{
|
|
745
|
+
at: runningAt,
|
|
746
|
+
event: "actor.running",
|
|
747
|
+
message: "Local Codex exec actor lanes are running; Observer data will refresh as sanitized evidence arrives."
|
|
748
|
+
}
|
|
749
|
+
],
|
|
750
|
+
lanes: lanes.map((lane) => ({
|
|
751
|
+
focus: lane.focus,
|
|
752
|
+
simId: lane.simId,
|
|
753
|
+
streamId: lane.streamId,
|
|
754
|
+
status: "running",
|
|
755
|
+
progress: 35,
|
|
756
|
+
currentStep: "Local Codex exec actor running",
|
|
757
|
+
summary: `Local Codex exec actor ${lane.focus.label} is running.`,
|
|
758
|
+
terminalTail: "Codex exec actor is running; sanitized transcript evidence will be linked after completion.",
|
|
759
|
+
updatedAt: runningAt,
|
|
760
|
+
completion: {
|
|
761
|
+
checkedAt: runningAt,
|
|
762
|
+
reason: "actor process is still running",
|
|
763
|
+
status: "running"
|
|
764
|
+
}
|
|
765
|
+
})),
|
|
766
|
+
events,
|
|
767
|
+
review: createLocalActorRunningReviewSummary(options.simCount === 1 ? "Codex exec" : "Codex exec fanout")
|
|
768
|
+
});
|
|
769
|
+
await writeRunBundleArtifacts(absoluteArtifactRoot, runningBundle);
|
|
770
|
+
const laneResults = await Promise.all(lanes.map(async (lane) => {
|
|
771
|
+
const actor = await executeLocalActorCommand(lane.command, {
|
|
772
|
+
cwd: options.cwd,
|
|
773
|
+
timeoutMs
|
|
774
|
+
});
|
|
775
|
+
const redactedTranscript = redactSensitiveText(actor.transcript);
|
|
776
|
+
const tail = tailText(redactedTranscript, 6_000);
|
|
777
|
+
const transcriptPath = options.simCount === 1
|
|
778
|
+
? "transcripts/codex-exec-sanitized.jsonl"
|
|
779
|
+
: `transcripts/${lane.streamId}-sanitized.jsonl`;
|
|
780
|
+
const tracePath = options.simCount === 1 ? "actor.json" : `actors/${lane.streamId}.json`;
|
|
781
|
+
await writeFile(path.join(absoluteArtifactRoot, transcriptPath), redactedTranscript.length > 0 ? redactedTranscript : "No transcript output captured.\n", "utf8");
|
|
782
|
+
await writeJson(path.join(absoluteArtifactRoot, tracePath), {
|
|
783
|
+
schema: "mimetic.local-codex-exec-actor.v1",
|
|
784
|
+
actor: "codex-exec",
|
|
785
|
+
commandName: lane.command.name,
|
|
786
|
+
focusId: lane.focus.id,
|
|
787
|
+
promptDigest: lane.promptDigest,
|
|
788
|
+
startedAt: createdAt,
|
|
789
|
+
completedAt: new Date().toISOString(),
|
|
790
|
+
durationMs: actor.durationMs,
|
|
791
|
+
exitCode: actor.exitCode,
|
|
792
|
+
signal: actor.signal,
|
|
793
|
+
status: actor.status,
|
|
794
|
+
timeoutMs,
|
|
795
|
+
transcriptBytes: actor.transcriptBytes,
|
|
796
|
+
transcriptPath,
|
|
797
|
+
redaction: "passed"
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
actor,
|
|
801
|
+
command: lane.command,
|
|
802
|
+
focus: lane.focus,
|
|
803
|
+
promptDigest: lane.promptDigest,
|
|
804
|
+
simId: lane.simId,
|
|
805
|
+
streamId: lane.streamId,
|
|
806
|
+
tail,
|
|
807
|
+
tracePath,
|
|
808
|
+
transcriptPath
|
|
809
|
+
};
|
|
810
|
+
}));
|
|
811
|
+
const completedAt = new Date().toISOString();
|
|
812
|
+
const laneStatuses = laneResults.map((result) => result.actor.status);
|
|
813
|
+
const status = aggregateActorStatus(laneStatuses);
|
|
814
|
+
const verdictReason = options.simCount === 1
|
|
815
|
+
? laneResults[0]?.actor.reason ?? "actor did not return a result"
|
|
816
|
+
: summarizeExecFanout(laneStatuses);
|
|
817
|
+
for (const result of laneResults) {
|
|
818
|
+
pushEvent("actor.observation", `Captured ${result.actor.transcriptBytes} output byte${result.actor.transcriptBytes === 1 ? "" : "s"}; sanitized transcript tail recorded with redaction=passed.`, "info", result.simId, result.streamId);
|
|
819
|
+
pushEvent("actor.artifact", "Wrote sanitized Codex exec transcript and actor trace artifacts under the ignored run directory.", "info", result.simId, result.streamId);
|
|
820
|
+
pushEvent("actor.verdict", `Local Codex exec actor lane ${result.focus.label} verdict ${result.actor.status}: ${result.actor.reason}`, result.actor.status === "passed" ? "info" : result.actor.status === "timed_out" ? "warn" : "error", result.simId, result.streamId);
|
|
821
|
+
pushEvent(result.actor.status === "timed_out" ? "actor.timeout" : "actor.exited", result.actor.status === "timed_out"
|
|
822
|
+
? `Actor timed out after ${timeoutMs}ms; last safe observation retained.`
|
|
823
|
+
: `Actor exited with code ${result.actor.exitCode ?? "null"}${result.actor.signal ? ` and signal ${result.actor.signal}` : ""}.`, result.actor.status === "passed" ? "info" : "warn", result.simId, result.streamId);
|
|
824
|
+
}
|
|
825
|
+
await writeFile(eventsPath, `${events.map((event) => JSON.stringify(event)).join("\n")}\n`, "utf8");
|
|
826
|
+
const bundle = buildLocalCodexExecBundle({
|
|
827
|
+
runId,
|
|
828
|
+
simCount: options.simCount,
|
|
829
|
+
createdAt,
|
|
830
|
+
cwd: options.cwd,
|
|
831
|
+
artifactRoot,
|
|
832
|
+
packageName,
|
|
833
|
+
mimeticSource,
|
|
834
|
+
persona: selection.persona,
|
|
835
|
+
scenario: selection.scenario,
|
|
836
|
+
lifecycle: [
|
|
837
|
+
...baseLifecycle,
|
|
838
|
+
{
|
|
839
|
+
at: completedAt,
|
|
840
|
+
event: "review.skeleton.created",
|
|
841
|
+
message: "Created review skeleton from sanitized actor lifecycle evidence."
|
|
842
|
+
}
|
|
843
|
+
],
|
|
844
|
+
lanes: laneResults.map((result) => ({
|
|
845
|
+
focus: result.focus,
|
|
846
|
+
simId: result.simId,
|
|
847
|
+
streamId: result.streamId,
|
|
848
|
+
status: result.actor.status,
|
|
849
|
+
progress: 100,
|
|
850
|
+
currentStep: result.actor.status === "passed" ? "Local Codex exec actor completed" : "Local Codex exec actor needs review",
|
|
851
|
+
summary: `Local Codex exec actor ${result.focus.label} ${result.actor.status}: ${result.actor.reason}`,
|
|
852
|
+
terminalTail: result.tail,
|
|
853
|
+
updatedAt: completedAt,
|
|
854
|
+
transcriptPath: result.transcriptPath,
|
|
855
|
+
tracePath: result.tracePath,
|
|
856
|
+
completion: {
|
|
857
|
+
checkedAt: completedAt,
|
|
858
|
+
...(result.actor.exitCode === undefined ? {} : { exitCode: result.actor.exitCode }),
|
|
859
|
+
logTail: result.tail,
|
|
860
|
+
reason: result.actor.reason,
|
|
861
|
+
status: result.actor.status
|
|
862
|
+
}
|
|
863
|
+
})),
|
|
864
|
+
events,
|
|
865
|
+
review: createLocalActorReviewSummary(options.simCount === 1 ? "Codex exec" : "Codex exec fanout", status, verdictReason)
|
|
866
|
+
});
|
|
867
|
+
await writeRunBundleArtifacts(absoluteArtifactRoot, bundle);
|
|
868
|
+
return {
|
|
869
|
+
schema: "mimetic.run-result.v1",
|
|
870
|
+
ok: status === "passed",
|
|
871
|
+
runId,
|
|
872
|
+
mode: "live",
|
|
873
|
+
simCount: options.simCount,
|
|
874
|
+
cwd: options.cwd,
|
|
875
|
+
artifactRoot,
|
|
876
|
+
bundlePath: path.join(artifactRoot, "run.json"),
|
|
877
|
+
reviewPath: path.join(artifactRoot, "review.md"),
|
|
878
|
+
latestPath: path.join(".mimetic", "runs", "latest.json"),
|
|
879
|
+
warnings,
|
|
880
|
+
...(status === "passed"
|
|
881
|
+
? {}
|
|
882
|
+
: {
|
|
883
|
+
error: {
|
|
884
|
+
code: "MIMETIC_LOCAL_CODEX_EXEC_FAILED",
|
|
885
|
+
message: `Local Codex exec actor ${status}: ${verdictReason}`
|
|
886
|
+
}
|
|
887
|
+
})
|
|
888
|
+
};
|
|
889
|
+
}
|
|
165
890
|
function buildSyntheticObserverFixtures(args) {
|
|
166
891
|
const templates = [
|
|
167
892
|
{
|
|
168
893
|
kind: "ui",
|
|
169
|
-
mode: "
|
|
894
|
+
mode: "browser-sim",
|
|
170
895
|
label: "UI journey",
|
|
171
896
|
currentStep: "Route and viewport contract captured",
|
|
172
|
-
summary: "
|
|
897
|
+
summary: "Browser lane reserved for VNC playback, screenshots, route state, and interaction trace.",
|
|
173
898
|
tail: "open target app\nresolve first-run route\ncapture viewport state\nrecord interaction trace",
|
|
174
899
|
viewport: { width: 1440, height: 960, deviceScaleFactor: 1 }
|
|
175
900
|
},
|
|
@@ -193,7 +918,7 @@ function buildSyntheticObserverFixtures(args) {
|
|
|
193
918
|
},
|
|
194
919
|
{
|
|
195
920
|
kind: "codex-ui",
|
|
196
|
-
mode: "codex-
|
|
921
|
+
mode: "codex-app-sim",
|
|
197
922
|
label: "Codex UI",
|
|
198
923
|
currentStep: "App-server embed contract captured",
|
|
199
924
|
summary: "Codex UI lane reserved for app-server sessions that can be watched beside terminal evidence.",
|
|
@@ -307,6 +1032,481 @@ function streamTransport(kind) {
|
|
|
307
1032
|
return "polling";
|
|
308
1033
|
return "snapshot";
|
|
309
1034
|
}
|
|
1035
|
+
function resolveLocalCodexTuiCommand(cwd, prompt, overrideCommand) {
|
|
1036
|
+
const envCommand = process.env.MIMETIC_CODEX_ACTOR_COMMAND;
|
|
1037
|
+
const commandParts = overrideCommand && overrideCommand.length > 0
|
|
1038
|
+
? overrideCommand
|
|
1039
|
+
: envCommand
|
|
1040
|
+
? parseCommandLine(envCommand)
|
|
1041
|
+
: defaultLocalCodexTuiCommand(cwd, prompt);
|
|
1042
|
+
const [command, ...args] = commandParts;
|
|
1043
|
+
if (!command) {
|
|
1044
|
+
const [fallbackCommand, ...fallbackArgs] = defaultLocalCodexTuiCommand(cwd, prompt);
|
|
1045
|
+
return {
|
|
1046
|
+
command: fallbackCommand ?? "codex",
|
|
1047
|
+
args: fallbackArgs,
|
|
1048
|
+
name: fallbackCommand ? path.basename(fallbackCommand) : "codex"
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
command,
|
|
1053
|
+
args,
|
|
1054
|
+
name: path.basename(command)
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function defaultLocalCodexTuiCommand(cwd, prompt) {
|
|
1058
|
+
const codexParts = [
|
|
1059
|
+
"codex",
|
|
1060
|
+
"--no-alt-screen",
|
|
1061
|
+
"-C",
|
|
1062
|
+
cwd,
|
|
1063
|
+
"--sandbox",
|
|
1064
|
+
"read-only",
|
|
1065
|
+
"--ask-for-approval",
|
|
1066
|
+
"never",
|
|
1067
|
+
prompt
|
|
1068
|
+
];
|
|
1069
|
+
if (process.platform === "linux") {
|
|
1070
|
+
return ["script", "-qfec", shellJoin(codexParts), "/dev/null"];
|
|
1071
|
+
}
|
|
1072
|
+
return codexParts;
|
|
1073
|
+
}
|
|
1074
|
+
function resolveLocalCodexExecCommand(cwd, prompt, overrideCommand) {
|
|
1075
|
+
const envCommand = process.env.MIMETIC_CODEX_ACTOR_COMMAND;
|
|
1076
|
+
const commandParts = overrideCommand && overrideCommand.length > 0
|
|
1077
|
+
? overrideCommand
|
|
1078
|
+
: envCommand
|
|
1079
|
+
? parseCommandLine(envCommand)
|
|
1080
|
+
: defaultLocalCodexExecCommand(cwd, prompt);
|
|
1081
|
+
const [command, ...args] = commandParts;
|
|
1082
|
+
if (!command) {
|
|
1083
|
+
const [fallbackCommand, ...fallbackArgs] = defaultLocalCodexExecCommand(cwd, prompt);
|
|
1084
|
+
return {
|
|
1085
|
+
command: fallbackCommand ?? "codex",
|
|
1086
|
+
args: fallbackArgs,
|
|
1087
|
+
name: fallbackCommand ? path.basename(fallbackCommand) : "codex"
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return {
|
|
1091
|
+
command,
|
|
1092
|
+
args,
|
|
1093
|
+
name: path.basename(command)
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function defaultLocalCodexExecCommand(cwd, prompt) {
|
|
1097
|
+
return [
|
|
1098
|
+
"codex",
|
|
1099
|
+
"exec",
|
|
1100
|
+
"--skip-git-repo-check",
|
|
1101
|
+
"--ignore-rules",
|
|
1102
|
+
"--ephemeral",
|
|
1103
|
+
"-C",
|
|
1104
|
+
cwd,
|
|
1105
|
+
"--sandbox",
|
|
1106
|
+
"read-only",
|
|
1107
|
+
"--json",
|
|
1108
|
+
prompt
|
|
1109
|
+
];
|
|
1110
|
+
}
|
|
1111
|
+
function executeLocalActorCommand(command, options) {
|
|
1112
|
+
const startedAt = Date.now();
|
|
1113
|
+
let transcript = "";
|
|
1114
|
+
let transcriptBytes = 0;
|
|
1115
|
+
let terminalQueryBuffer = "";
|
|
1116
|
+
let observedMarkerStatus = null;
|
|
1117
|
+
let stoppingAfterMarker = false;
|
|
1118
|
+
let timedOut = false;
|
|
1119
|
+
let settled = false;
|
|
1120
|
+
let timer;
|
|
1121
|
+
let markerKillTimer;
|
|
1122
|
+
return new Promise((resolve) => {
|
|
1123
|
+
const finish = (result) => {
|
|
1124
|
+
if (settled) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
settled = true;
|
|
1128
|
+
clearTimeout(timer);
|
|
1129
|
+
clearTimeout(markerKillTimer);
|
|
1130
|
+
const normalizedTranscript = normalizeLocalActorTranscript(transcript);
|
|
1131
|
+
resolve({
|
|
1132
|
+
...result,
|
|
1133
|
+
durationMs: Date.now() - startedAt,
|
|
1134
|
+
transcript: redactSensitiveText(normalizedTranscript),
|
|
1135
|
+
transcriptBytes
|
|
1136
|
+
});
|
|
1137
|
+
};
|
|
1138
|
+
const child = spawn(command.command, command.args, {
|
|
1139
|
+
cwd: options.cwd,
|
|
1140
|
+
env: {
|
|
1141
|
+
...process.env,
|
|
1142
|
+
TERM: process.env.TERM ?? "xterm-256color",
|
|
1143
|
+
COLUMNS: process.env.COLUMNS ?? "120",
|
|
1144
|
+
LINES: process.env.LINES ?? "40",
|
|
1145
|
+
...(options.verdictNonce ? { MIMETIC_ACTOR_VERDICT_NONCE: options.verdictNonce } : {})
|
|
1146
|
+
},
|
|
1147
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1148
|
+
});
|
|
1149
|
+
timer = setTimeout(() => {
|
|
1150
|
+
timedOut = true;
|
|
1151
|
+
child.kill("SIGTERM");
|
|
1152
|
+
setTimeout(() => {
|
|
1153
|
+
if (timedOut) {
|
|
1154
|
+
child.kill("SIGKILL");
|
|
1155
|
+
}
|
|
1156
|
+
}, 2_000).unref();
|
|
1157
|
+
}, options.timeoutMs);
|
|
1158
|
+
timer.unref();
|
|
1159
|
+
const capture = (chunk) => {
|
|
1160
|
+
transcriptBytes += chunk.byteLength;
|
|
1161
|
+
transcript = limitTranscript(transcript + chunk.toString("utf8"));
|
|
1162
|
+
terminalQueryBuffer = respondToTerminalQueries(terminalQueryBuffer, chunk, child.stdin);
|
|
1163
|
+
const markerStatus = observedMarkerStatus ?? extractLocalActorVerdict(normalizeLocalActorTranscript(transcript), options.verdictNonce);
|
|
1164
|
+
if (markerStatus && !observedMarkerStatus) {
|
|
1165
|
+
observedMarkerStatus = markerStatus;
|
|
1166
|
+
stoppingAfterMarker = true;
|
|
1167
|
+
child.kill("SIGTERM");
|
|
1168
|
+
markerKillTimer = setTimeout(() => {
|
|
1169
|
+
child.kill("SIGKILL");
|
|
1170
|
+
}, 2_000);
|
|
1171
|
+
markerKillTimer.unref();
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
child.stdout?.on("data", capture);
|
|
1175
|
+
child.stderr?.on("data", capture);
|
|
1176
|
+
child.once("error", (error) => {
|
|
1177
|
+
finish({
|
|
1178
|
+
status: "blocked",
|
|
1179
|
+
reason: `actor command could not start: ${error.message}`
|
|
1180
|
+
});
|
|
1181
|
+
});
|
|
1182
|
+
child.once("close", (code, signal) => {
|
|
1183
|
+
if (timedOut || (Date.now() - startedAt >= options.timeoutMs && code === null)) {
|
|
1184
|
+
timedOut = false;
|
|
1185
|
+
finish({
|
|
1186
|
+
status: "timed_out",
|
|
1187
|
+
reason: `actor exceeded ${options.timeoutMs}ms timeout`,
|
|
1188
|
+
...(signal === null ? {} : { signal })
|
|
1189
|
+
});
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const markerStatus = observedMarkerStatus ?? extractLocalActorVerdict(normalizeLocalActorTranscript(transcript), options.verdictNonce);
|
|
1193
|
+
const processFailed = code !== 0 && !(stoppingAfterMarker && markerStatus);
|
|
1194
|
+
const status = processFailed
|
|
1195
|
+
? markerStatus === "blocked" ? "blocked" : "failed"
|
|
1196
|
+
: markerStatus ?? "passed";
|
|
1197
|
+
finish({
|
|
1198
|
+
status,
|
|
1199
|
+
reason: processFailed
|
|
1200
|
+
? markerStatus === "blocked"
|
|
1201
|
+
? `actor reported blocked verdict marker and exited with code ${code ?? "null"}`
|
|
1202
|
+
: `actor process exited with code ${code ?? "null"}`
|
|
1203
|
+
: markerStatus
|
|
1204
|
+
? `actor reported ${markerStatus} verdict marker`
|
|
1205
|
+
: "actor process exited successfully",
|
|
1206
|
+
...(code === null ? {} : { exitCode: code }),
|
|
1207
|
+
...(signal === null ? {} : { signal })
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
function respondToTerminalQueries(currentBuffer, chunk, stdin) {
|
|
1213
|
+
if (!stdin || !stdin.writable) {
|
|
1214
|
+
return "";
|
|
1215
|
+
}
|
|
1216
|
+
let buffer = `${currentBuffer}${chunk.toString("latin1")}`;
|
|
1217
|
+
while (true) {
|
|
1218
|
+
const cprIndex = buffer.indexOf("\x1b[6n");
|
|
1219
|
+
const oscMatch = /\x1b\](10|11|12);\?(?:\x07|\x1b\\)/.exec(buffer);
|
|
1220
|
+
const oscIndex = oscMatch?.index ?? -1;
|
|
1221
|
+
if (cprIndex === -1 && oscIndex === -1) {
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
1224
|
+
if (cprIndex !== -1 && (oscIndex === -1 || cprIndex < oscIndex)) {
|
|
1225
|
+
stdin.write("\x1b[24;120R");
|
|
1226
|
+
buffer = buffer.slice(cprIndex + "\x1b[6n".length);
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const colorSlot = oscMatch?.[1] ?? "10";
|
|
1230
|
+
stdin.write(terminalColorResponse(colorSlot));
|
|
1231
|
+
buffer = buffer.slice((oscIndex === -1 ? 0 : oscIndex) + (oscMatch?.[0].length ?? 0));
|
|
1232
|
+
}
|
|
1233
|
+
return buffer.slice(-128);
|
|
1234
|
+
}
|
|
1235
|
+
function terminalColorResponse(slot) {
|
|
1236
|
+
if (slot === "11") {
|
|
1237
|
+
return "\x1b]11;rgb:0000/0000/0000\x07";
|
|
1238
|
+
}
|
|
1239
|
+
return `\x1b]${slot};rgb:ffff/ffff/ffff\x07`;
|
|
1240
|
+
}
|
|
1241
|
+
function normalizeLocalActorTranscript(transcript) {
|
|
1242
|
+
return transcript
|
|
1243
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
1244
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
1245
|
+
.replace(/\x1b[78=>]/g, "")
|
|
1246
|
+
.replace(/\r\n/g, "\n")
|
|
1247
|
+
.replace(/\r/g, "\n")
|
|
1248
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
1249
|
+
}
|
|
1250
|
+
function extractLocalActorVerdict(transcript, verdictNonce) {
|
|
1251
|
+
const compactTranscript = transcript.replace(/\s+/g, "");
|
|
1252
|
+
const match = verdictNonce
|
|
1253
|
+
? new RegExp(`MIMETIC_ACTOR_VERDICT=(passed|blocked|failed)MIMETIC_ACTOR_NONCE=${escapeRegExp(verdictNonce)}`, "i").exec(compactTranscript)
|
|
1254
|
+
: /MIMETIC_ACTOR_VERDICT=(passed|blocked|failed)/i.exec(compactTranscript);
|
|
1255
|
+
if (!match) {
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
return match[1]?.toLowerCase();
|
|
1259
|
+
}
|
|
1260
|
+
async function checkCodexWorkspaceTrust(cwd) {
|
|
1261
|
+
if (process.env.MIMETIC_SKIP_CODEX_TRUST_PREFLIGHT === "1") {
|
|
1262
|
+
return { ok: true };
|
|
1263
|
+
}
|
|
1264
|
+
const trustRoot = await detectCodexTrustRoot(cwd);
|
|
1265
|
+
if (!trustRoot) {
|
|
1266
|
+
return { ok: true };
|
|
1267
|
+
}
|
|
1268
|
+
const configPath = path.join(process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"), "config.toml");
|
|
1269
|
+
const configText = await readTextIfExists(configPath);
|
|
1270
|
+
if (configText && codexConfigTrustsProject(configText, trustRoot)) {
|
|
1271
|
+
return { ok: true };
|
|
1272
|
+
}
|
|
1273
|
+
return {
|
|
1274
|
+
ok: false,
|
|
1275
|
+
trustRoot,
|
|
1276
|
+
message: `Codex workspace trust preflight blocked local TUI launch; trust root is not explicitly trusted as an exact Codex project root: ${trustRoot}`,
|
|
1277
|
+
recoveryCommand: `codex --no-alt-screen -C ${shellQuote(trustRoot)}`
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
async function detectCodexTrustRoot(cwd) {
|
|
1281
|
+
const worktreeRoot = await findGitWorktreeRoot(cwd);
|
|
1282
|
+
if (!worktreeRoot) {
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
const dotGitPath = path.join(worktreeRoot, ".git");
|
|
1286
|
+
if (await directoryExists(dotGitPath)) {
|
|
1287
|
+
return worktreeRoot;
|
|
1288
|
+
}
|
|
1289
|
+
const gitFile = await readTextIfExists(dotGitPath);
|
|
1290
|
+
if (!gitFile?.startsWith("gitdir:")) {
|
|
1291
|
+
return worktreeRoot;
|
|
1292
|
+
}
|
|
1293
|
+
const gitDir = gitFile.slice("gitdir:".length).trim();
|
|
1294
|
+
const absoluteGitDir = path.isAbsolute(gitDir) ? gitDir : path.resolve(worktreeRoot, gitDir);
|
|
1295
|
+
const commonDirText = await readTextIfExists(path.join(absoluteGitDir, "commondir"));
|
|
1296
|
+
if (!commonDirText) {
|
|
1297
|
+
return worktreeRoot;
|
|
1298
|
+
}
|
|
1299
|
+
const commonDir = commonDirText.trim();
|
|
1300
|
+
const absoluteCommonDir = path.resolve(absoluteGitDir, commonDir);
|
|
1301
|
+
return path.basename(absoluteCommonDir) === ".git" ? path.dirname(absoluteCommonDir) : worktreeRoot;
|
|
1302
|
+
}
|
|
1303
|
+
async function findGitWorktreeRoot(cwd) {
|
|
1304
|
+
let current = path.resolve(cwd);
|
|
1305
|
+
while (true) {
|
|
1306
|
+
if (await fileExists(path.join(current, ".git")) || await directoryExists(path.join(current, ".git"))) {
|
|
1307
|
+
return current;
|
|
1308
|
+
}
|
|
1309
|
+
const parent = path.dirname(current);
|
|
1310
|
+
if (parent === current) {
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
current = parent;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function codexConfigTrustsProject(configText, trustRoot) {
|
|
1317
|
+
const sectionPattern = /^\[projects\."((?:\\.|[^"\\])*)"\]\s*$/gm;
|
|
1318
|
+
let sectionMatch;
|
|
1319
|
+
while ((sectionMatch = sectionPattern.exec(configText)) !== null) {
|
|
1320
|
+
const projectPath = unescapeTomlString(sectionMatch[1] ?? "");
|
|
1321
|
+
const afterSection = configText.slice(sectionMatch.index + sectionMatch[0].length);
|
|
1322
|
+
const nextSectionIndex = afterSection.search(/^\[/m);
|
|
1323
|
+
const sectionBody = nextSectionIndex === -1 ? afterSection : afterSection.slice(0, nextSectionIndex);
|
|
1324
|
+
if (/^trust_level\s*=\s*"trusted"\s*$/m.test(sectionBody) && isSamePath(projectPath, trustRoot)) {
|
|
1325
|
+
return true;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
function unescapeTomlString(value) {
|
|
1331
|
+
return value.replace(/\\(["\\])/g, "$1");
|
|
1332
|
+
}
|
|
1333
|
+
function isSamePath(candidatePath, targetPath) {
|
|
1334
|
+
const candidate = path.resolve(candidatePath);
|
|
1335
|
+
const target = path.resolve(targetPath);
|
|
1336
|
+
return candidate === target;
|
|
1337
|
+
}
|
|
1338
|
+
function normalizeActorTimeout(value) {
|
|
1339
|
+
if (!Number.isInteger(value) || value === undefined || value < 1 || value > LOCAL_CODEX_TUI_MAX_TIMEOUT_MS) {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
return value;
|
|
1343
|
+
}
|
|
1344
|
+
function readEnvInteger(name) {
|
|
1345
|
+
const value = process.env[name];
|
|
1346
|
+
if (value === undefined || value.trim() === "") {
|
|
1347
|
+
return undefined;
|
|
1348
|
+
}
|
|
1349
|
+
return /^\d+$/.test(value) ? Number.parseInt(value, 10) : Number.NaN;
|
|
1350
|
+
}
|
|
1351
|
+
function buildLocalCodexTuiPrompt(selection, verdictNonce) {
|
|
1352
|
+
return [
|
|
1353
|
+
"You are a Mimetic local Codex TUI dogfood actor.",
|
|
1354
|
+
`Persona: ${selection.persona.name}.`,
|
|
1355
|
+
`Scenario: ${selection.scenario.title}.`,
|
|
1356
|
+
"Inspect this public repository's Mimetic setup, run evidence, and Observer affordances.",
|
|
1357
|
+
"Run at most two read-only inspection commands; prefer file reads, `node dist/cli.js --help`, or `pnpm typecheck` when available.",
|
|
1358
|
+
"Do not run commands that write runtime artifacts or temp config, including `pnpm mimetic`, `mimetic watch`, `mimetic feedback`, `mimetic init`, tests, builds, installs, or commands that write `.mimetic/`.",
|
|
1359
|
+
"If the strongest proof would require writes in this read-only sandbox, inspect existing artifacts instead and name the write-required proof as a follow-up.",
|
|
1360
|
+
"Use passed when read-only inspection confirms the committed harness and existing evidence contract; write-required follow-ups alone are not blockers.",
|
|
1361
|
+
"Do not print secrets, do not commit, do not push, do not open GitHub issues, and do not use private data.",
|
|
1362
|
+
"Finish by summarizing one public-safe harness improvement.",
|
|
1363
|
+
`Then print exactly one final machine-readable line in this format: MIMETIC_ACTOR_VERDICT=<status> MIMETIC_ACTOR_NONCE=${verdictNonce}.`,
|
|
1364
|
+
"Replace <status> with exactly one lowercase word: passed, blocked, or failed."
|
|
1365
|
+
].join(" ");
|
|
1366
|
+
}
|
|
1367
|
+
function localCodexExecFocus(index) {
|
|
1368
|
+
const focuses = [
|
|
1369
|
+
{
|
|
1370
|
+
id: "install-readability",
|
|
1371
|
+
label: "install/readability",
|
|
1372
|
+
instruction: "audit whether a new user can understand the committed Mimetic dogfood setup quickly",
|
|
1373
|
+
suggestedCommands: [
|
|
1374
|
+
"test -r mimetic/README.md && sed -n '1,40p' mimetic/README.md",
|
|
1375
|
+
"test -r mimetic/config.ts && wc -l mimetic/config.ts"
|
|
1376
|
+
]
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
id: "public-safety-trust",
|
|
1380
|
+
label: "public-safety/trust",
|
|
1381
|
+
instruction: "check the local actor boundaries, public-safety claims, and trust-bootstrap language",
|
|
1382
|
+
suggestedCommands: [
|
|
1383
|
+
"test -r mimetic/coverage-map.md && sed -n '1,80p' mimetic/coverage-map.md",
|
|
1384
|
+
"test -r docs/architecture/local-codex-tui-actor.md && sed -n '1,80p' docs/architecture/local-codex-tui-actor.md"
|
|
1385
|
+
]
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
id: "observer-evidence",
|
|
1389
|
+
label: "Observer/evidence",
|
|
1390
|
+
instruction: "inspect whether run evidence and Observer expectations are easy to verify",
|
|
1391
|
+
suggestedCommands: [
|
|
1392
|
+
"test -r mimetic/coverage-matrix.md && sed -n '1,80p' mimetic/coverage-matrix.md",
|
|
1393
|
+
"test -r mimetic/scenarios/onboarding-regression.yaml && sed -n '1,120p' mimetic/scenarios/onboarding-regression.yaml"
|
|
1394
|
+
]
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
id: "verification-release",
|
|
1398
|
+
label: "verification/release",
|
|
1399
|
+
instruction: "inspect the verification and release-gate promises exposed to local dogfood actors",
|
|
1400
|
+
suggestedCommands: [
|
|
1401
|
+
"test -r package.json && node -e \"const p=require('./package.json'); console.log(p.scripts.check); console.log(p.scripts['public-surface:scan']);\"",
|
|
1402
|
+
"test -r mimetic/README.md && sed -n '1,80p' mimetic/README.md"
|
|
1403
|
+
]
|
|
1404
|
+
}
|
|
1405
|
+
];
|
|
1406
|
+
return focuses[index % focuses.length] ?? focuses[0];
|
|
1407
|
+
}
|
|
1408
|
+
function aggregateActorStatus(statuses) {
|
|
1409
|
+
if (statuses.includes("failed"))
|
|
1410
|
+
return "failed";
|
|
1411
|
+
if (statuses.includes("timed_out"))
|
|
1412
|
+
return "timed_out";
|
|
1413
|
+
if (statuses.includes("blocked"))
|
|
1414
|
+
return "blocked";
|
|
1415
|
+
return statuses.length > 0 && statuses.every((status) => status === "passed") ? "passed" : "failed";
|
|
1416
|
+
}
|
|
1417
|
+
function summarizeExecFanout(statuses) {
|
|
1418
|
+
if (statuses.length === 1) {
|
|
1419
|
+
return statuses[0] === "passed" ? "actor process exited successfully" : `actor lane ${statuses[0]}`;
|
|
1420
|
+
}
|
|
1421
|
+
const counts = statuses.reduce((current, status) => ({ ...current, [status]: current[status] + 1 }), { passed: 0, failed: 0, blocked: 0, timed_out: 0 });
|
|
1422
|
+
const summary = Object.keys(counts)
|
|
1423
|
+
.filter((status) => counts[status] > 0)
|
|
1424
|
+
.map((status) => `${counts[status]} ${status}`)
|
|
1425
|
+
.join(", ");
|
|
1426
|
+
return statuses.every((status) => status === "passed")
|
|
1427
|
+
? `all ${statuses.length} Codex exec lanes passed`
|
|
1428
|
+
: `${statuses.length} Codex exec lanes completed with ${summary}`;
|
|
1429
|
+
}
|
|
1430
|
+
function buildLocalCodexExecPrompt(selection, lane) {
|
|
1431
|
+
const suggestedCommands = lane?.focus.suggestedCommands ?? [
|
|
1432
|
+
"test -r mimetic/config.ts && wc -l mimetic/config.ts",
|
|
1433
|
+
"test -r mimetic/README.md && sed -n '1,40p' mimetic/README.md"
|
|
1434
|
+
];
|
|
1435
|
+
return [
|
|
1436
|
+
"You are a Mimetic local Codex exec dogfood actor running noninteractively.",
|
|
1437
|
+
`Persona: ${selection.persona.name}.`,
|
|
1438
|
+
`Scenario: ${selection.scenario.title}.`,
|
|
1439
|
+
...(lane ? [`Fanout lane ${lane.index}/${lane.total}. Focus: ${lane.focus.instruction}.`] : []),
|
|
1440
|
+
"Run at most two read-only local inspection commands.",
|
|
1441
|
+
`Suggested commands: \`${suggestedCommands[0]}\` and \`${suggestedCommands[1]}\`.`,
|
|
1442
|
+
"Do not edit files, do not run network commands, do not commit, do not push, do not open GitHub issues, and do not print secrets.",
|
|
1443
|
+
"Do not inspect additional files unless one suggested command fails.",
|
|
1444
|
+
"Finish within three sentences with exactly one public-safe verdict line using passed, blocked, or failed."
|
|
1445
|
+
].join(" ");
|
|
1446
|
+
}
|
|
1447
|
+
function parseCommandLine(input) {
|
|
1448
|
+
const tokens = [];
|
|
1449
|
+
let current = "";
|
|
1450
|
+
let quote = null;
|
|
1451
|
+
let escaping = false;
|
|
1452
|
+
for (const char of input.trim()) {
|
|
1453
|
+
if (escaping) {
|
|
1454
|
+
current += char;
|
|
1455
|
+
escaping = false;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (char === "\\" && quote !== "'") {
|
|
1459
|
+
escaping = true;
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
if (quote) {
|
|
1463
|
+
if (char === quote) {
|
|
1464
|
+
quote = null;
|
|
1465
|
+
}
|
|
1466
|
+
else {
|
|
1467
|
+
current += char;
|
|
1468
|
+
}
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
if (char === "\"" || char === "'") {
|
|
1472
|
+
quote = char;
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
if (/\s/.test(char)) {
|
|
1476
|
+
if (current !== "") {
|
|
1477
|
+
tokens.push(current);
|
|
1478
|
+
current = "";
|
|
1479
|
+
}
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
current += char;
|
|
1483
|
+
}
|
|
1484
|
+
if (current !== "") {
|
|
1485
|
+
tokens.push(current);
|
|
1486
|
+
}
|
|
1487
|
+
return quote ? [] : tokens;
|
|
1488
|
+
}
|
|
1489
|
+
function shellJoin(parts) {
|
|
1490
|
+
return parts.map(shellQuote).join(" ");
|
|
1491
|
+
}
|
|
1492
|
+
function shellQuote(value) {
|
|
1493
|
+
if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(value)) {
|
|
1494
|
+
return value;
|
|
1495
|
+
}
|
|
1496
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1497
|
+
}
|
|
1498
|
+
function limitTranscript(value) {
|
|
1499
|
+
if (value.length <= LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS) {
|
|
1500
|
+
return value;
|
|
1501
|
+
}
|
|
1502
|
+
return `[...sanitized transcript truncated to last ${LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS} characters...]\n${value.slice(-LOCAL_ACTOR_TRANSCRIPT_MAX_CHARS)}`;
|
|
1503
|
+
}
|
|
1504
|
+
function tailText(value, maxChars) {
|
|
1505
|
+
if (value.length <= maxChars) {
|
|
1506
|
+
return value;
|
|
1507
|
+
}
|
|
1508
|
+
return value.slice(-maxChars);
|
|
1509
|
+
}
|
|
310
1510
|
function normalizeSimCount(value) {
|
|
311
1511
|
if (value === undefined) {
|
|
312
1512
|
return 1;
|
|
@@ -490,6 +1690,41 @@ function createReviewSummary() {
|
|
|
490
1690
|
]
|
|
491
1691
|
};
|
|
492
1692
|
}
|
|
1693
|
+
function createLocalActorRunningReviewSummary(actorLabel) {
|
|
1694
|
+
return {
|
|
1695
|
+
schema: REVIEW_SCHEMA,
|
|
1696
|
+
verdict: "contract_proof_only",
|
|
1697
|
+
summary: `Live local ${actorLabel} actor is running. This is an in-progress Observer snapshot; final verdict will be written after sanitized actor evidence is captured.`,
|
|
1698
|
+
gaps: [
|
|
1699
|
+
"Final actor verdict and transcript artifacts are not available until the run completes.",
|
|
1700
|
+
"Live Observer follow depends on polling observer/observer-data.json while this run is active.",
|
|
1701
|
+
"No GitHub mutation, target OSS mutation, E2B substrate, or production data is used by this local actor contract."
|
|
1702
|
+
]
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
function createLocalActorReviewSummary(actorLabel, status, reason) {
|
|
1706
|
+
const verdict = status === "passed"
|
|
1707
|
+
? "pass"
|
|
1708
|
+
: status === "timed_out"
|
|
1709
|
+
? "timed_out"
|
|
1710
|
+
: status === "blocked"
|
|
1711
|
+
? "blocked"
|
|
1712
|
+
: "fail";
|
|
1713
|
+
const isTui = actorLabel.toLowerCase().includes("tui");
|
|
1714
|
+
const isFanout = actorLabel.toLowerCase().includes("fanout");
|
|
1715
|
+
return {
|
|
1716
|
+
schema: REVIEW_SCHEMA,
|
|
1717
|
+
verdict,
|
|
1718
|
+
summary: `Live local ${actorLabel} actor ${status}: ${reason}. This proves the local actor lifecycle and sanitized evidence path${isFanout ? " across requested fanout lanes" : ""}, not target product behavior.`,
|
|
1719
|
+
gaps: [
|
|
1720
|
+
isTui
|
|
1721
|
+
? "Only one local Codex TUI actor is supported in this slice."
|
|
1722
|
+
: "Codex TUI trust bootstrap, PTY rendering, and keyboard-focus proof remain separate from the noninteractive exec actor.",
|
|
1723
|
+
"Live follow uses polling Observer snapshots; raw interactive terminal streaming remains a follow-up hardening step.",
|
|
1724
|
+
"No GitHub mutation, target OSS mutation, E2B substrate, or production data was used by this local actor contract."
|
|
1725
|
+
]
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
493
1728
|
async function loadDryRunSelection(cwd, mimeticSource) {
|
|
494
1729
|
const warnings = [];
|
|
495
1730
|
if (mimeticSource === "missing") {
|
|
@@ -531,10 +1766,12 @@ async function loadDryRunSelection(cwd, mimeticSource) {
|
|
|
531
1766
|
};
|
|
532
1767
|
}
|
|
533
1768
|
function renderReviewMarkdown(bundle) {
|
|
534
|
-
return `# Mimetic
|
|
1769
|
+
return `# Mimetic Run Review
|
|
535
1770
|
|
|
536
1771
|
Run: ${bundle.runId}
|
|
537
1772
|
|
|
1773
|
+
Mode: ${bundle.mode}
|
|
1774
|
+
|
|
538
1775
|
Verdict: ${bundle.review.verdict}
|
|
539
1776
|
|
|
540
1777
|
${bundle.review.summary}
|
|
@@ -607,6 +1844,13 @@ async function readTextIfExists(filePath) {
|
|
|
607
1844
|
async function writeJson(filePath, value) {
|
|
608
1845
|
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
609
1846
|
}
|
|
1847
|
+
async function writeRunBundleArtifacts(absoluteArtifactRoot, bundle) {
|
|
1848
|
+
await writeJson(path.join(absoluteArtifactRoot, "run.json"), bundle);
|
|
1849
|
+
await writeJson(path.join(absoluteArtifactRoot, "review.json"), bundle.review);
|
|
1850
|
+
await writeFile(path.join(absoluteArtifactRoot, "review.md"), renderReviewMarkdown(bundle), "utf8");
|
|
1851
|
+
await mkdir(path.join(absoluteArtifactRoot, "observer"), { recursive: true });
|
|
1852
|
+
await writeJson(path.join(absoluteArtifactRoot, "observer", "observer-data.json"), buildObserverData(bundle));
|
|
1853
|
+
}
|
|
610
1854
|
async function validateCwd(cwd) {
|
|
611
1855
|
try {
|
|
612
1856
|
const stats = await stat(cwd);
|
|
@@ -653,11 +1897,17 @@ async function fileExists(filePath) {
|
|
|
653
1897
|
function containsSensitivePattern(text) {
|
|
654
1898
|
return sensitivePatterns.some((pattern) => pattern.test(text));
|
|
655
1899
|
}
|
|
1900
|
+
function redactSensitiveText(text) {
|
|
1901
|
+
return sensitivePatterns.reduce((current, pattern) => {
|
|
1902
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
1903
|
+
return current.replace(new RegExp(pattern.source, flags), "[REDACTED_SECRET]");
|
|
1904
|
+
}, text);
|
|
1905
|
+
}
|
|
656
1906
|
function isRunBundle(value) {
|
|
657
1907
|
return isRecord(value)
|
|
658
1908
|
&& value.schema === RUN_BUNDLE_SCHEMA
|
|
659
1909
|
&& typeof value.runId === "string"
|
|
660
|
-
&& value.mode === "dry-run"
|
|
1910
|
+
&& (value.mode === "dry-run" || value.mode === "live")
|
|
661
1911
|
&& typeof value.createdAt === "string"
|
|
662
1912
|
&& isRecord(value.review)
|
|
663
1913
|
&& isReviewSummary(value.review)
|
|
@@ -667,7 +1917,11 @@ function isRunBundle(value) {
|
|
|
667
1917
|
function isReviewSummary(value) {
|
|
668
1918
|
return isRecord(value)
|
|
669
1919
|
&& value.schema === REVIEW_SCHEMA
|
|
670
|
-
&& value.verdict === "contract_proof_only"
|
|
1920
|
+
&& (value.verdict === "contract_proof_only"
|
|
1921
|
+
|| value.verdict === "pass"
|
|
1922
|
+
|| value.verdict === "fail"
|
|
1923
|
+
|| value.verdict === "blocked"
|
|
1924
|
+
|| value.verdict === "timed_out")
|
|
671
1925
|
&& typeof value.summary === "string"
|
|
672
1926
|
&& Array.isArray(value.gaps)
|
|
673
1927
|
&& value.gaps.every((gap) => typeof gap === "string");
|