codex-orchestrator 2.0.4 → 2.0.6
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/CHANGELOG.md +50 -0
- package/README.md +30 -3
- package/dist/src/v2/acceptance-proof.d.ts +12 -0
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +120 -6
- package/dist/src/v2/acceptance-proof.js.map +1 -1
- package/dist/src/v2/adapters/command.d.ts +2 -0
- package/dist/src/v2/adapters/command.d.ts.map +1 -1
- package/dist/src/v2/adapters/command.js +67 -8
- package/dist/src/v2/adapters/command.js.map +1 -1
- package/dist/src/v2/adapters/gh-issue-adapter.js +16 -5
- package/dist/src/v2/adapters/gh-issue-adapter.js.map +1 -1
- package/dist/src/v2/adapters/gh-pull-request-adapter.d.ts +7 -1
- package/dist/src/v2/adapters/gh-pull-request-adapter.d.ts.map +1 -1
- package/dist/src/v2/adapters/gh-pull-request-adapter.js +288 -0
- package/dist/src/v2/adapters/gh-pull-request-adapter.js.map +1 -1
- package/dist/src/v2/adapters/pull-requests.d.ts +69 -0
- package/dist/src/v2/adapters/pull-requests.d.ts.map +1 -1
- package/dist/src/v2/adapters/pull-requests.js +48 -0
- package/dist/src/v2/adapters/pull-requests.js.map +1 -1
- package/dist/src/v2/adapters/worktree.d.ts +1 -0
- package/dist/src/v2/adapters/worktree.d.ts.map +1 -1
- package/dist/src/v2/adapters/worktree.js +10 -1
- package/dist/src/v2/adapters/worktree.js.map +1 -1
- package/dist/src/v2/android-proof-runner.d.ts +93 -0
- package/dist/src/v2/android-proof-runner.d.ts.map +1 -0
- package/dist/src/v2/android-proof-runner.js +921 -0
- package/dist/src/v2/android-proof-runner.js.map +1 -0
- package/dist/src/v2/cli.d.ts +9 -0
- package/dist/src/v2/cli.d.ts.map +1 -1
- package/dist/src/v2/cli.js +38 -11
- package/dist/src/v2/cli.js.map +1 -1
- package/dist/src/v2/config.d.ts +13 -1
- package/dist/src/v2/config.d.ts.map +1 -1
- package/dist/src/v2/config.js +57 -4
- package/dist/src/v2/config.js.map +1 -1
- package/dist/src/v2/containment.d.ts +11 -2
- package/dist/src/v2/containment.d.ts.map +1 -1
- package/dist/src/v2/containment.js +35 -7
- package/dist/src/v2/containment.js.map +1 -1
- package/dist/src/v2/direct-delivery.d.ts +1 -1
- package/dist/src/v2/direct-delivery.d.ts.map +1 -1
- package/dist/src/v2/direct-delivery.js +9 -3
- package/dist/src/v2/direct-delivery.js.map +1 -1
- package/dist/src/v2/immutable-workflow-publisher.js +6 -1
- package/dist/src/v2/immutable-workflow-publisher.js.map +1 -1
- package/dist/src/v2/mobile-lease.d.ts +9 -0
- package/dist/src/v2/mobile-lease.d.ts.map +1 -1
- package/dist/src/v2/mobile-lease.js +27 -3
- package/dist/src/v2/mobile-lease.js.map +1 -1
- package/dist/src/v2/review-feedback-coordinator.d.ts +54 -0
- package/dist/src/v2/review-feedback-coordinator.d.ts.map +1 -0
- package/dist/src/v2/review-feedback-coordinator.js +245 -0
- package/dist/src/v2/review-feedback-coordinator.js.map +1 -0
- package/dist/src/v2/review-feedback.d.ts +127 -0
- package/dist/src/v2/review-feedback.d.ts.map +1 -0
- package/dist/src/v2/review-feedback.js +436 -0
- package/dist/src/v2/review-feedback.js.map +1 -0
- package/dist/src/v2/run-issue.d.ts +61 -0
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +843 -66
- package/dist/src/v2/run-issue.js.map +1 -1
- package/dist/src/v2/run-store.d.ts +48 -1
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +125 -5
- package/dist/src/v2/run-store.js.map +1 -1
- package/dist/src/v2/runtime.d.ts +37 -0
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +183 -15
- package/dist/src/v2/runtime.js.map +1 -1
- package/dist/src/v2/setup.js +1 -1
- package/dist/src/v2/setup.js.map +1 -1
- package/docs/deep-dive.md +71 -7
- package/internal-workflow/docs/agents/contract-test-ledger.md +11 -1
- package/internal-workflow/evals/coding-skill-evals.json +18 -0
- package/internal-workflow/manifest.json +1 -1
- package/internal-workflow/skills/acceptance-proof/SKILL.md +1 -1
- package/internal-workflow/skills/acceptance-proof/references/android.md +6 -6
- package/internal-workflow/skills/code-debugger/SKILL.md +3 -3
- package/internal-workflow/skills/code-review/SKILL.md +18 -6
- package/internal-workflow/skills/implementation-spec-maker/SKILL.md +10 -8
- package/internal-workflow/skills/implementation-spec-maker/agents/openai.yaml +1 -1
- package/internal-workflow/skills/implementation-spec-review/SKILL.md +17 -3
- package/internal-workflow/skills/implementation-spec-review/evals/evals.json +30 -0
- package/internal-workflow/skills/implementation-spec-review/references/review-loop.md +30 -6
- package/internal-workflow/skills/spec-implementer/references/review-loop.md +7 -1
- package/internal-workflow/skills/tdd/SKILL.md +5 -4
- package/internal-workflow/skills/tdd/evals/evals.json +18 -0
- package/internal-workflow/skills/tdd/mocking.md +3 -42
- package/internal-workflow/skills/tdd/refactoring.md +6 -8
- package/package.json +1 -1
- package/internal-workflow/skills/acceptance-proof/tools/android-lease.mjs +0 -280
package/dist/src/v2/runtime.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { constants } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { lstat, mkdir, open, readFile, readdir, readlink, realpath, rm } from 'node:fs/promises';
|
|
4
|
+
import { access, lstat, mkdir, open, readFile, readdir, readlink, realpath, rm } from 'node:fs/promises';
|
|
5
5
|
import { homedir, hostname } from 'node:os';
|
|
6
6
|
import { basename, dirname, isAbsolute, join, posix, relative, resolve } from 'node:path';
|
|
7
7
|
import { writeDurableAtomicFile } from './adapters/durable-atomic-file.js';
|
|
8
8
|
import { GitWorktreeManager } from './adapters/worktree.js';
|
|
9
|
+
import { ReviewFeedbackCoordinator } from './review-feedback-coordinator.js';
|
|
9
10
|
import { defaultProcessExecutor } from './adapters/command.js';
|
|
11
|
+
import { RunnerAndroidProofController } from './android-proof-runner.js';
|
|
10
12
|
import { AcceptanceProof, ProofQuiescenceError } from './acceptance-proof.js';
|
|
11
13
|
import { createCheckedChangeCapabilities } from './checked-change.js';
|
|
12
14
|
import { InjectedContainedReportOperation } from './contained-report-operation.js';
|
|
@@ -47,6 +49,30 @@ export class LocalGitRunIssueAdapter {
|
|
|
47
49
|
requiredBaseSha: input.baseSha,
|
|
48
50
|
});
|
|
49
51
|
}
|
|
52
|
+
async ensureContinuationWorktree(input) {
|
|
53
|
+
const localHead = (await this.git([
|
|
54
|
+
'-C', input.targetRoot, 'rev-parse', '--verify', `refs/heads/${input.branchName}`,
|
|
55
|
+
])).trim();
|
|
56
|
+
const remoteHead = await this.getRemoteBranchSha(input.targetRoot, input.branchName);
|
|
57
|
+
if (localHead !== input.publishedHeadSha || remoteHead !== input.publishedHeadSha) {
|
|
58
|
+
throw new Error('continuation branch refs do not exactly match the published head');
|
|
59
|
+
}
|
|
60
|
+
await this.worktrees.ensureIssueWorktree({
|
|
61
|
+
targetRoot: input.targetRoot,
|
|
62
|
+
workspacePath: input.worktreePath,
|
|
63
|
+
branchName: input.branchName,
|
|
64
|
+
baseBranch: input.baseBranch,
|
|
65
|
+
requiredBaseSha: input.publishedHeadSha,
|
|
66
|
+
allowResume: true,
|
|
67
|
+
});
|
|
68
|
+
const observed = await this.inspectWorktree({
|
|
69
|
+
worktreePath: input.worktreePath,
|
|
70
|
+
branchName: input.branchName,
|
|
71
|
+
baseSha: input.publishedHeadSha,
|
|
72
|
+
});
|
|
73
|
+
if (observed !== 'matching')
|
|
74
|
+
throw new Error('continuation worktree does not match the published head');
|
|
75
|
+
}
|
|
50
76
|
async inspectWorktree(input) {
|
|
51
77
|
try {
|
|
52
78
|
const stat = await lstat(input.worktreePath);
|
|
@@ -110,6 +136,9 @@ export class LocalGitRunIssueAdapter {
|
|
|
110
136
|
listChangedFiles(worktreePath) {
|
|
111
137
|
return this.worktrees.listChangedFiles(worktreePath);
|
|
112
138
|
}
|
|
139
|
+
listChangedFilesIgnoringUntrackedRoot(worktreePath, ignoredRoot) {
|
|
140
|
+
return this.worktrees.listChangedFilesIgnoringUntrackedRoot(worktreePath, ignoredRoot);
|
|
141
|
+
}
|
|
113
142
|
async fingerprintChangedFiles(worktreePath, changedFiles) {
|
|
114
143
|
const root = await realpath(worktreePath);
|
|
115
144
|
const entries = [];
|
|
@@ -193,6 +222,12 @@ export class ContainedImplementationAgent {
|
|
|
193
222
|
bootId: this.dependencies.bootId,
|
|
194
223
|
});
|
|
195
224
|
const baseline = await this.dependencies.git.snapshot(input.worktreePath);
|
|
225
|
+
await input.onPrepared?.({
|
|
226
|
+
attemptId,
|
|
227
|
+
reportPath: attempt.reportPath,
|
|
228
|
+
preparedAt: (this.dependencies.now ?? (() => new Date().toISOString()))(),
|
|
229
|
+
baseline,
|
|
230
|
+
});
|
|
196
231
|
try {
|
|
197
232
|
const result = await (this.dependencies.process ?? new CodexProcess()).run({
|
|
198
233
|
codexPath: config.codex.command,
|
|
@@ -210,6 +245,8 @@ export class ContainedImplementationAgent {
|
|
|
210
245
|
`The operation's immutable workflow root is ${attempt.workflowRoot}.`,
|
|
211
246
|
`Implement issue #${input.issue.number}: ${input.issue.title}`,
|
|
212
247
|
`Implementation cycle: ${input.cycle}.`,
|
|
248
|
+
...(input.reviewFeedbackRound ? [`Pull-request feedback repair round: ${input.reviewFeedbackRound}.`] : []),
|
|
249
|
+
...(input.reviewFeedback?.length ? [`Frozen trusted pull-request feedback: ${canonicalJson(input.reviewFeedback)}`] : []),
|
|
213
250
|
`Frozen acceptance criteria: ${canonicalJson(input.frozenCriteria)}`,
|
|
214
251
|
...(input.reworkFindings.length > 0 ? [`Repair these verified findings: ${canonicalJson(input.reworkFindings)}`] : []),
|
|
215
252
|
...(input.repairOnly ? ['Report repair only: do not modify any worktree file; emit a schema-valid implementation report for the existing change.'] : []),
|
|
@@ -219,6 +256,9 @@ export class ContainedImplementationAgent {
|
|
|
219
256
|
idleTimeoutMs: config.codex.idleTimeoutMs,
|
|
220
257
|
operationPolicy: attempt.policy,
|
|
221
258
|
executionProfile: attempt.profile,
|
|
259
|
+
...(input.onLaunched ? { onSpawned: ({ pid, processGroupId }) => input.onLaunched({
|
|
260
|
+
attemptId, pid, processGroupId, launchedAt: (this.dependencies.now ?? (() => new Date().toISOString()))(),
|
|
261
|
+
}) } : {}),
|
|
222
262
|
}, input.signal);
|
|
223
263
|
if (result.kind === 'cancelled')
|
|
224
264
|
return { kind: 'cancelled' };
|
|
@@ -248,6 +288,26 @@ export class ContainedImplementationAgent {
|
|
|
248
288
|
};
|
|
249
289
|
}
|
|
250
290
|
}
|
|
291
|
+
async recover(input) {
|
|
292
|
+
const expected = join(resolve(this.dependencies.orchestratorHome), 'v2', sha256(input.canonicalRepository), 'runs', input.runId, 'attempts', input.invocation.attemptId, 'report.json');
|
|
293
|
+
if (resolve(input.invocation.reportPath) !== expected) {
|
|
294
|
+
throw new Error('review feedback implementation report path is not attempt-owned');
|
|
295
|
+
}
|
|
296
|
+
let bytes;
|
|
297
|
+
try {
|
|
298
|
+
bytes = await readRegularFile(expected);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (isErrorCode(error, 'ENOENT'))
|
|
302
|
+
return undefined;
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
kind: 'completed',
|
|
307
|
+
attemptId: input.invocation.attemptId,
|
|
308
|
+
report: decodeAgentReportForValidation(bytes),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
251
311
|
}
|
|
252
312
|
export class ContainedProofAgent {
|
|
253
313
|
dependencies;
|
|
@@ -271,8 +331,6 @@ export class ContainedProofAgent {
|
|
|
271
331
|
});
|
|
272
332
|
const artifactRoot = resolve(worktreePath, config.proof.artifactDir);
|
|
273
333
|
const snapshotRoot = dirname(attempt.sourceSkillPath ?? attempt.operationPath);
|
|
274
|
-
const androidLeaseRoot = join(resolve(this.dependencies.orchestratorHome), 'v2', sha256(canonicalRepository), 'leases');
|
|
275
|
-
const androidLeaseArtifact = join(artifactRoot, input.proofId, 'android-lease.json');
|
|
276
334
|
const iosLeaseRoot = join(resolve(this.dependencies.orchestratorHome), 'v2', sha256(canonicalRepository), 'leases');
|
|
277
335
|
const iosLeaseArtifact = join(artifactRoot, input.proofId, 'ios-lease.json');
|
|
278
336
|
const iosTooling = await discoverIosTooling(this.dependencies.processExecutor, this.dependencies.iosXcrunPath);
|
|
@@ -300,12 +358,14 @@ export class ContainedProofAgent {
|
|
|
300
358
|
`Write evidence only below ${config.proof.artifactDir}.`,
|
|
301
359
|
'When a frozen criterion has a browser surface, follow references/browser.md from the exact acceptance-proof skill snapshot.',
|
|
302
360
|
'When a frozen criterion has an Android surface, follow references/android.md from the exact acceptance-proof skill snapshot.',
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
361
|
+
...(config.proof.android ? [
|
|
362
|
+
`Runner-owned Android artifact paths: ${canonicalJson(input.runnerPreparedArtifactPaths)}.`,
|
|
363
|
+
`Runner-owned Android preparation warnings: ${canonicalJson(input.runnerPreparationWarnings)}.`,
|
|
364
|
+
'The trusted Runner owns emulator, build, adb, lease, capture, and cleanup actions. Do not invoke adb, emulator, Flutter run, or an Android lease helper.',
|
|
365
|
+
'Inspect Runner artifacts when present. If preparation warnings are present, continue with all available non-visual evidence and preserve the warning as a residual risk; Android infrastructure failure alone must not block delivery.',
|
|
366
|
+
] : [
|
|
367
|
+
'Runner-owned Android proof is not configured for this repository. Do not invoke adb, emulator, Flutter run, or an Android lease helper; return a typed tool blocker for Android criteria.',
|
|
368
|
+
]),
|
|
309
369
|
'When a frozen criterion has an iOS surface, follow references/ios.md from the exact acceptance-proof skill snapshot.',
|
|
310
370
|
`iOS lease helper: ${join(snapshotRoot, 'tools', 'ios-lease.mjs')}.`,
|
|
311
371
|
`iOS lease root: ${iosLeaseRoot}.`,
|
|
@@ -347,11 +407,43 @@ export class ContainedProofAgent {
|
|
|
347
407
|
}
|
|
348
408
|
}
|
|
349
409
|
}
|
|
410
|
+
export async function resolveCodexExecutable(command, safePath) {
|
|
411
|
+
if (command.length === 0 || safePath.length === 0)
|
|
412
|
+
throw new Error('Codex command and safe path must be non-empty');
|
|
413
|
+
if (!isAbsolute(command) && command.includes('/'))
|
|
414
|
+
throw new Error('relative Codex command paths are not supported');
|
|
415
|
+
const candidates = isAbsolute(command)
|
|
416
|
+
? [command]
|
|
417
|
+
: safePath.split(':').filter((entry) => entry.length > 0).map((entry) => join(entry, command));
|
|
418
|
+
for (const candidate of candidates) {
|
|
419
|
+
try {
|
|
420
|
+
const canonical = await realpath(candidate);
|
|
421
|
+
const stat = await lstat(canonical);
|
|
422
|
+
if (!stat.isFile())
|
|
423
|
+
continue;
|
|
424
|
+
await access(canonical, constants.X_OK);
|
|
425
|
+
return canonical;
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
// Try the next safe-path entry.
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
throw new Error('Codex executable is unavailable in the configured safe path');
|
|
432
|
+
}
|
|
350
433
|
export function createV2Runtime(input) {
|
|
351
434
|
const targetRoot = resolve(input.targetRoot);
|
|
352
435
|
const orchestratorHome = resolve(input.orchestratorHome);
|
|
353
436
|
const now = input.now ?? (() => new Date().toISOString());
|
|
354
437
|
const commandExecutor = input.processExecutor ?? defaultProcessExecutor;
|
|
438
|
+
const runtimeSafePath = requireRuntimeString(input.safePath, 'safePath');
|
|
439
|
+
let codexExecutable;
|
|
440
|
+
const resolveRuntimeCodex = async (command) => {
|
|
441
|
+
if (codexExecutable?.command === command)
|
|
442
|
+
return codexExecutable.path;
|
|
443
|
+
const path = await resolveCodexExecutable(command, runtimeSafePath);
|
|
444
|
+
codexExecutable = { command, path };
|
|
445
|
+
return path;
|
|
446
|
+
};
|
|
355
447
|
const git = input.git ?? new LocalGitRunIssueAdapter(commandExecutor);
|
|
356
448
|
const proofFreshnessGit = git;
|
|
357
449
|
const controller = new AbortController();
|
|
@@ -364,12 +456,13 @@ export function createV2Runtime(input) {
|
|
|
364
456
|
?? (process.env.ANDROID_SDK_ROOT ? join(process.env.ANDROID_SDK_ROOT, 'platform-tools', 'adb') : undefined)
|
|
365
457
|
?? join(homedir(), 'Library', 'Android', 'sdk', 'platform-tools', 'adb');
|
|
366
458
|
const androidAdbPath = resolve(configuredAndroidAdbPath);
|
|
459
|
+
const androidEmulatorPath = join(dirname(dirname(androidAdbPath)), 'emulator', 'emulator');
|
|
367
460
|
const iosXcrunPath = resolve(input.iosXcrunPath ?? '/usr/bin/xcrun');
|
|
368
461
|
const containedDependencies = () => ({
|
|
369
462
|
config: () => requireConfig(currentConfig),
|
|
370
463
|
orchestratorHome,
|
|
371
464
|
parentCodexHome: requireRuntimeString(input.parentCodexHome, 'parentCodexHome'),
|
|
372
|
-
safePath:
|
|
465
|
+
safePath: runtimeSafePath,
|
|
373
466
|
bootId: input.bootId,
|
|
374
467
|
process: containedProcess,
|
|
375
468
|
createAttemptId: input.createAttemptId,
|
|
@@ -386,6 +479,12 @@ export function createV2Runtime(input) {
|
|
|
386
479
|
iosXcrunPath,
|
|
387
480
|
processExecutor: commandExecutor,
|
|
388
481
|
});
|
|
482
|
+
const androidProofController = new RunnerAndroidProofController({
|
|
483
|
+
adbPath: androidAdbPath,
|
|
484
|
+
emulatorPath: androidEmulatorPath,
|
|
485
|
+
execute: commandExecutor,
|
|
486
|
+
now: () => new Date(now()),
|
|
487
|
+
});
|
|
389
488
|
const reportOperation = new InjectedContainedReportOperation({
|
|
390
489
|
prepare: async ({ operation, attemptId, runId, workflowGeneration }) => ({
|
|
391
490
|
operation,
|
|
@@ -676,7 +775,11 @@ export function createV2Runtime(input) {
|
|
|
676
775
|
if (resolve(requestedRoot) !== targetRoot)
|
|
677
776
|
throw new Error('runtime target root mismatch');
|
|
678
777
|
const bytes = await readRegularFile(join(targetRoot, '.codex-orchestrator', 'config.json'));
|
|
679
|
-
const
|
|
778
|
+
const parsed = parseAgentAutoConfig(parseJsonWithoutDuplicateKeys(bytes.toString('utf8')));
|
|
779
|
+
const config = {
|
|
780
|
+
...parsed,
|
|
781
|
+
codex: { ...parsed.codex, command: await resolveRuntimeCodex(parsed.codex.command) },
|
|
782
|
+
};
|
|
680
783
|
const path = join(targetRoot, config.runner.stateDir, 'v2', 'run-state.json');
|
|
681
784
|
if (!runRecords)
|
|
682
785
|
runRecords = new FileRunRecordWriter(path);
|
|
@@ -708,6 +811,7 @@ export function createV2Runtime(input) {
|
|
|
708
811
|
worktreeRoot: worktreePath,
|
|
709
812
|
now: () => new Date(now()),
|
|
710
813
|
artifactRelativePathForProof: (proofId) => `${config.proof.artifactDir}/${proofId}/android-lease.json`,
|
|
814
|
+
targetController: androidProofController,
|
|
711
815
|
});
|
|
712
816
|
const iosLease = new FileIosLeaseVerifier({
|
|
713
817
|
leaseRoot: join(orchestratorHome, 'v2', repoKey, 'leases'),
|
|
@@ -737,18 +841,56 @@ export function createV2Runtime(input) {
|
|
|
737
841
|
now,
|
|
738
842
|
signal: controller.signal,
|
|
739
843
|
});
|
|
740
|
-
|
|
844
|
+
const androidRelevant = !!config.proof.android && isAndroidProofRelevant(proofInput.issue, proofInput.frozenCriteria, checked.payload.changedFiles);
|
|
845
|
+
const runnerPreparedArtifactPaths = [];
|
|
846
|
+
const runnerPreparedArtifactSha256 = {};
|
|
847
|
+
const runnerPreparationWarnings = [];
|
|
848
|
+
let preparationAttempted = false;
|
|
849
|
+
return acceptanceProof.proveChange({
|
|
850
|
+
...proofInput,
|
|
851
|
+
runnerPreparedArtifactPaths,
|
|
852
|
+
runnerPreparedArtifactSha256,
|
|
853
|
+
runnerPreparationWarnings,
|
|
854
|
+
beforeAgentLaunch: async () => {
|
|
855
|
+
await proofInput.beforeAgentLaunch?.();
|
|
856
|
+
if (!androidRelevant || preparationAttempted)
|
|
857
|
+
return;
|
|
858
|
+
preparationAttempted = true;
|
|
859
|
+
const prepared = await androidProofController.prepare({
|
|
860
|
+
proofId: proofInput.proofId,
|
|
861
|
+
worktreePath,
|
|
862
|
+
artifactDir: config.proof.artifactDir,
|
|
863
|
+
leaseRoot: join(orchestratorHome, 'v2', repoKey, 'leases'),
|
|
864
|
+
config: config.proof.android,
|
|
865
|
+
checks: checked.payload.checks,
|
|
866
|
+
checkedChangeSha256: checked.checkedChangeSha256,
|
|
867
|
+
proofAgentBudgetMs: config.codex.timeoutMs * 3,
|
|
868
|
+
signal: controller.signal,
|
|
869
|
+
});
|
|
870
|
+
if (prepared.status === 'prepared') {
|
|
871
|
+
runnerPreparedArtifactPaths.splice(0, runnerPreparedArtifactPaths.length, ...prepared.runnerPreparedArtifactPaths);
|
|
872
|
+
Object.assign(runnerPreparedArtifactSha256, prepared.runnerPreparedArtifactSha256);
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
runnerPreparationWarnings.push(`Android UI proof unfinished: ${prepared.summary}`);
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
});
|
|
741
879
|
},
|
|
742
880
|
};
|
|
743
881
|
const runner = new RunIssue({
|
|
744
882
|
readConfig,
|
|
745
883
|
validateContainment: async (config) => {
|
|
746
884
|
const certificate = await readContainmentCertificate(containmentCertificatePath(orchestratorHome));
|
|
747
|
-
const
|
|
885
|
+
const executablePath = await resolveRuntimeCodex(config.codex.command);
|
|
886
|
+
const version = await commandExecutor(executablePath, ['--version']);
|
|
748
887
|
if (version.exitCode !== 0)
|
|
749
888
|
throw new Error('Codex version is unavailable');
|
|
750
889
|
assertContainmentCertificateMatchesRuntime(certificate, {
|
|
751
890
|
codexVersion: version.stdout.trim(),
|
|
891
|
+
codexExecutablePath: executablePath,
|
|
892
|
+
codexExecutableSha256: sha256(await readFile(executablePath)),
|
|
893
|
+
packageVersion: input.packageVersion,
|
|
752
894
|
argvPolicySha256: containmentArgvPolicySha256(),
|
|
753
895
|
});
|
|
754
896
|
},
|
|
@@ -777,7 +919,13 @@ export function createV2Runtime(input) {
|
|
|
777
919
|
url: issue.url,
|
|
778
920
|
state: 'OPEN',
|
|
779
921
|
labels: issue.labels.map((label) => label.name).sort(),
|
|
780
|
-
comments: comments.map((comment) => ({
|
|
922
|
+
comments: comments.map((comment) => ({
|
|
923
|
+
id: comment.id,
|
|
924
|
+
body: comment.body,
|
|
925
|
+
authorAssociation: comment.authorAssociation,
|
|
926
|
+
createdAt: comment.createdAt,
|
|
927
|
+
updatedAt: comment.updatedAt,
|
|
928
|
+
})),
|
|
781
929
|
};
|
|
782
930
|
},
|
|
783
931
|
setLabels: async (issueNumber, labels) => {
|
|
@@ -796,10 +944,26 @@ export function createV2Runtime(input) {
|
|
|
796
944
|
if (matches.length > 1)
|
|
797
945
|
throw new Error('multiple open pull requests match publication intent');
|
|
798
946
|
const match = matches[0];
|
|
799
|
-
|
|
947
|
+
if (!match)
|
|
948
|
+
return undefined;
|
|
949
|
+
const reviewTarget = await input.pullRequests.getReviewTarget(match.number);
|
|
950
|
+
return {
|
|
951
|
+
url: match.url,
|
|
952
|
+
body: match.body,
|
|
953
|
+
number: match.number,
|
|
954
|
+
nodeId: match.nodeId,
|
|
955
|
+
...(reviewTarget ? { headSha: reviewTarget.headRefOid } : {}),
|
|
956
|
+
};
|
|
800
957
|
},
|
|
801
958
|
createDraft: async ({ title, body, headBranch, baseBranch }) => input.pullRequests.createDraftPullRequest({ title, body, headBranch, baseBranch }),
|
|
959
|
+
listConversationComments: async (number) => (await input.pullRequests.listConversationComments(number))
|
|
960
|
+
.map((comment) => ({ id: comment.id, body: comment.body })),
|
|
961
|
+
postConversationComment: async (number, body) => {
|
|
962
|
+
const comment = await input.pullRequests.postConversationComment(number, body);
|
|
963
|
+
return { id: comment.id, body: comment.body };
|
|
964
|
+
},
|
|
802
965
|
},
|
|
966
|
+
reviewFeedback: new ReviewFeedbackCoordinator({ pullRequests: input.pullRequests, issues: input.issues, now }),
|
|
803
967
|
git,
|
|
804
968
|
routeCoordinator: {
|
|
805
969
|
run: ({ state, ...routeInput }) => new RouteCoordinator({
|
|
@@ -1332,6 +1496,10 @@ async function scrubReportReadView(directory) {
|
|
|
1332
1496
|
}
|
|
1333
1497
|
}
|
|
1334
1498
|
}
|
|
1499
|
+
function isAndroidProofRelevant(issue, criteria, changedFiles) {
|
|
1500
|
+
return /\bandroid\b/iu.test([issue.title, issue.body, ...criteria.map((criterion) => criterion.text)].join('\n'))
|
|
1501
|
+
|| changedFiles.some((path) => /^(?:android\/|lib\/|assets\/|fonts\/|pubspec\.(?:yaml|lock)$)/u.test(path));
|
|
1502
|
+
}
|
|
1335
1503
|
function isErrorCode(error, code) {
|
|
1336
1504
|
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
|
|
1337
1505
|
}
|