codex-orchestrator 2.0.5 → 2.0.7
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 +57 -0
- package/README.md +6 -2
- package/dist/src/v2/acceptance-proof.d.ts +7 -0
- package/dist/src/v2/acceptance-proof.d.ts.map +1 -1
- package/dist/src/v2/acceptance-proof.js +111 -5
- 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/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/checked-change.d.ts +1 -1
- package/dist/src/v2/checked-change.d.ts.map +1 -1
- package/dist/src/v2/checked-change.js +3 -2
- package/dist/src/v2/checked-change.js.map +1 -1
- package/dist/src/v2/code-review-report.d.ts +1 -2
- package/dist/src/v2/code-review-report.d.ts.map +1 -1
- package/dist/src/v2/code-review-report.js +6 -11
- package/dist/src/v2/code-review-report.js.map +1 -1
- package/dist/src/v2/config.d.ts +13 -0
- package/dist/src/v2/config.d.ts.map +1 -1
- package/dist/src/v2/config.js +57 -1
- package/dist/src/v2/config.js.map +1 -1
- package/dist/src/v2/containment.d.ts +0 -52
- package/dist/src/v2/containment.d.ts.map +1 -1
- package/dist/src/v2/containment.js +1 -195
- package/dist/src/v2/containment.js.map +1 -1
- package/dist/src/v2/direct-delivery.d.ts +11 -2
- package/dist/src/v2/direct-delivery.d.ts.map +1 -1
- package/dist/src/v2/direct-delivery.js +62 -17
- 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/implementation-reviewer.d.ts +1 -1
- package/dist/src/v2/implementation-reviewer.d.ts.map +1 -1
- package/dist/src/v2/implementation-reviewer.js +1 -2
- package/dist/src/v2/implementation-reviewer.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/proof-report.d.ts +1 -1
- package/dist/src/v2/proof-report.d.ts.map +1 -1
- package/dist/src/v2/proof-report.js +6 -4
- package/dist/src/v2/proof-report.js.map +1 -1
- package/dist/src/v2/run-issue.d.ts +18 -1
- package/dist/src/v2/run-issue.d.ts.map +1 -1
- package/dist/src/v2/run-issue.js +504 -109
- package/dist/src/v2/run-issue.js.map +1 -1
- package/dist/src/v2/run-store.d.ts +17 -1
- package/dist/src/v2/run-store.d.ts.map +1 -1
- package/dist/src/v2/run-store.js +30 -4
- package/dist/src/v2/run-store.js.map +1 -1
- package/dist/src/v2/runtime.d.ts.map +1 -1
- package/dist/src/v2/runtime.js +84 -27
- package/dist/src/v2/runtime.js.map +1 -1
- package/docs/deep-dive.md +25 -11
- package/internal-workflow/manifest.json +1 -1
- package/internal-workflow/schemas/proof-report-v1.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/implementation-spec-maker/SKILL.md +10 -8
- 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/package.json +1 -2
- package/internal-workflow/skills/acceptance-proof/tools/android-lease.mjs +0 -280
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { execFile } from 'node:child_process';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
5
|
-
import { lstat, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
6
|
-
import { dirname, join, resolve } from 'node:path';
|
|
7
|
-
import { promisify } from 'node:util';
|
|
8
|
-
|
|
9
|
-
const execFileAsync = promisify(execFile);
|
|
10
|
-
const LEASE_FILE = 'android.json';
|
|
11
|
-
const LEASE_MS = 15 * 60 * 1000;
|
|
12
|
-
|
|
13
|
-
class Blocked extends Error {}
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const [command, ...argv] = process.argv.slice(2);
|
|
17
|
-
const args = parseArgs(argv);
|
|
18
|
-
const result = await execute(command, args);
|
|
19
|
-
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
const blocked = error instanceof Blocked;
|
|
22
|
-
process.stdout.write(`${JSON.stringify({
|
|
23
|
-
status: blocked ? 'blocked' : 'internal-error',
|
|
24
|
-
reason: blocked ? error.message : 'Android lease helper failed internally.',
|
|
25
|
-
})}\n`);
|
|
26
|
-
process.exitCode = blocked ? 20 : 70;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function execute(command, args) {
|
|
30
|
-
const leaseRoot = requiredPath(args['lease-root'], 'lease root');
|
|
31
|
-
const artifactPath = requiredPath(args.artifact, 'lease artifact');
|
|
32
|
-
const proofId = requiredString(args['proof-id'], 'proof ID');
|
|
33
|
-
const adb = args.adb
|
|
34
|
-
? requiredPath(args.adb, 'adb path')
|
|
35
|
-
: process.env.ANDROID_ADB || join(process.env.HOME || '', 'Library', 'Android', 'sdk', 'platform-tools', 'adb');
|
|
36
|
-
const now = parseNow();
|
|
37
|
-
await ensureDirectDirectory(leaseRoot);
|
|
38
|
-
await ensureDirectDirectory(dirname(artifactPath));
|
|
39
|
-
const leasePath = join(leaseRoot, LEASE_FILE);
|
|
40
|
-
|
|
41
|
-
if (command === 'acquire') {
|
|
42
|
-
const appId = requiredAppId(args['app-id']);
|
|
43
|
-
const ownerPid = requiredPid(args['owner-pid']);
|
|
44
|
-
const target = await selectEmulator(adb);
|
|
45
|
-
const existing = await readLease(leasePath);
|
|
46
|
-
if (existing) {
|
|
47
|
-
if (existing.proofId === proofId && existing.appId === appId && existing.ownerPid === ownerPid) {
|
|
48
|
-
await verifyTarget(adb, existing, false);
|
|
49
|
-
return publicResult('reused', existing);
|
|
50
|
-
}
|
|
51
|
-
if (!(Date.parse(existing.expiresAt) < now.getTime() && !pidAlive(existing.ownerPid))) {
|
|
52
|
-
throw new Blocked('An active foreign Android lease already exists.');
|
|
53
|
-
}
|
|
54
|
-
if (existing.serial !== target.serial || existing.appId !== appId) {
|
|
55
|
-
throw new Blocked('A stale Android lease does not match the requested target identity.');
|
|
56
|
-
}
|
|
57
|
-
await rm(leasePath);
|
|
58
|
-
}
|
|
59
|
-
const livePid = await appPid(adb, target.serial, appId);
|
|
60
|
-
if (livePid !== undefined) throw new Blocked('The requested Android app already has a user-owned live process.');
|
|
61
|
-
const lease = {
|
|
62
|
-
schema: 'codex-orchestrator.android-lease',
|
|
63
|
-
version: 1,
|
|
64
|
-
status: 'active',
|
|
65
|
-
proofId,
|
|
66
|
-
token: randomUUID(),
|
|
67
|
-
serial: target.serial,
|
|
68
|
-
appId,
|
|
69
|
-
ownerPid,
|
|
70
|
-
appPid: null,
|
|
71
|
-
acquiredAt: now.toISOString(),
|
|
72
|
-
expiresAt: new Date(now.getTime() + LEASE_MS).toISOString(),
|
|
73
|
-
updatedAt: now.toISOString(),
|
|
74
|
-
};
|
|
75
|
-
await createExclusiveJson(leasePath, lease);
|
|
76
|
-
await writeAtomicJson(artifactPath, lease);
|
|
77
|
-
return publicResult('acquired', lease);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const token = requiredString(args.token, 'lease token');
|
|
81
|
-
const lease = await requireOwnedLease(leasePath, proofId, token, now);
|
|
82
|
-
if (command === 'bind') {
|
|
83
|
-
await verifyTarget(adb, lease, false, true);
|
|
84
|
-
const pid = await appPid(adb, lease.serial, lease.appId);
|
|
85
|
-
if (pid === undefined) throw new Blocked('The leased Android app is not running.');
|
|
86
|
-
const bound = { ...lease, appPid: pid, updatedAt: now.toISOString() };
|
|
87
|
-
await replaceJson(leasePath, bound);
|
|
88
|
-
await writeAtomicJson(artifactPath, bound);
|
|
89
|
-
return publicResult('bound', bound);
|
|
90
|
-
}
|
|
91
|
-
if (command === 'verify') {
|
|
92
|
-
await verifyTarget(adb, lease, true);
|
|
93
|
-
await writeAtomicJson(artifactPath, { ...lease, updatedAt: now.toISOString() });
|
|
94
|
-
return publicResult('verified', lease);
|
|
95
|
-
}
|
|
96
|
-
if (command === 'release') {
|
|
97
|
-
await verifyTarget(adb, lease, Number.isSafeInteger(lease.appPid));
|
|
98
|
-
const released = { ...lease, status: 'released', updatedAt: now.toISOString() };
|
|
99
|
-
await writeAtomicJson(artifactPath, released);
|
|
100
|
-
await rm(leasePath);
|
|
101
|
-
await syncDirectory(dirname(leasePath));
|
|
102
|
-
return publicResult('released', released);
|
|
103
|
-
}
|
|
104
|
-
throw new Blocked('Unknown Android lease command.');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function selectEmulator(adb) {
|
|
108
|
-
const output = await adbCommand(adb, ['devices', '-l']);
|
|
109
|
-
const rows = output.stdout.split(/\r?\n/u).slice(1).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
110
|
-
const [serial, state] = line.split(/\s+/u);
|
|
111
|
-
return { serial, state };
|
|
112
|
-
});
|
|
113
|
-
if (rows.some((row) => !row.serial.startsWith('emulator-'))) throw new Blocked('Physical Android devices are not eligible for runner leases.');
|
|
114
|
-
if (rows.length !== 1 || rows[0].state !== 'device') throw new Blocked('Exactly one online Android emulator is required.');
|
|
115
|
-
const boot = await adbCommand(adb, ['-s', rows[0].serial, 'shell', 'getprop', 'sys.boot_completed']);
|
|
116
|
-
if (boot.stdout.trim() !== '1') throw new Blocked('The Android emulator has not completed boot.');
|
|
117
|
-
return rows[0];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function verifyTarget(adb, lease, requireBoundApp, allowUnboundLive = false) {
|
|
121
|
-
const target = await selectEmulator(adb);
|
|
122
|
-
if (target.serial !== lease.serial) throw new Blocked('The leased Android emulator identity changed.');
|
|
123
|
-
const currentPid = await appPid(adb, lease.serial, lease.appId);
|
|
124
|
-
if (requireBoundApp) {
|
|
125
|
-
if (!Number.isSafeInteger(lease.appPid) || currentPid !== lease.appPid) {
|
|
126
|
-
throw new Blocked('The leased Android app PID changed or disappeared.');
|
|
127
|
-
}
|
|
128
|
-
} else if (!allowUnboundLive && lease.appPid === null && currentPid !== undefined) {
|
|
129
|
-
throw new Blocked('The leased Android app became live before runner binding.');
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function appPid(adb, serial, appId) {
|
|
134
|
-
const result = await adbCommand(adb, ['-s', serial, 'shell', 'pidof', '-s', appId], true);
|
|
135
|
-
const value = result.stdout.trim();
|
|
136
|
-
if (!value) return undefined;
|
|
137
|
-
const pid = Number(value);
|
|
138
|
-
if (!Number.isSafeInteger(pid) || pid < 1) throw new Blocked('Android app PID observation is invalid.');
|
|
139
|
-
return pid;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function adbCommand(adb, args, allowFailure = false) {
|
|
143
|
-
try {
|
|
144
|
-
return await execFileAsync(adb, args, { timeout: 15_000, maxBuffer: 1024 * 1024, env: process.env });
|
|
145
|
-
} catch (error) {
|
|
146
|
-
if (allowFailure && typeof error === 'object' && error !== null && 'stdout' in error) {
|
|
147
|
-
return { stdout: String(error.stdout || ''), stderr: String(error.stderr || '') };
|
|
148
|
-
}
|
|
149
|
-
throw new Blocked('Android tooling did not return a usable target observation.');
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function parseArgs(argv) {
|
|
154
|
-
const result = {};
|
|
155
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
156
|
-
const arg = argv[index];
|
|
157
|
-
if (!arg.startsWith('--')) throw new Blocked('Android lease arguments are invalid.');
|
|
158
|
-
const equals = arg.indexOf('=');
|
|
159
|
-
if (equals > 2) {
|
|
160
|
-
result[arg.slice(2, equals)] = arg.slice(equals + 1);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
const value = argv[++index];
|
|
164
|
-
if (!value || value.startsWith('--')) throw new Blocked('Android lease argument value is missing.');
|
|
165
|
-
result[arg.slice(2)] = value;
|
|
166
|
-
}
|
|
167
|
-
return result;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function parseNow() {
|
|
171
|
-
const value = process.env.CODEX_ORCHESTRATOR_NOW || new Date().toISOString();
|
|
172
|
-
const date = new Date(value);
|
|
173
|
-
if (Number.isNaN(date.getTime()) || date.toISOString() !== value) throw new Error('invalid lease clock');
|
|
174
|
-
return date;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async function requireOwnedLease(path, proofId, token, now) {
|
|
178
|
-
const lease = await readLease(path);
|
|
179
|
-
if (!lease || lease.proofId !== proofId || lease.token !== token) throw new Blocked('Android lease ownership does not match.');
|
|
180
|
-
if (lease.status !== 'active' || Date.parse(lease.expiresAt) < now.getTime()) throw new Blocked('Android lease is not active.');
|
|
181
|
-
return lease;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async function readLease(path) {
|
|
185
|
-
try {
|
|
186
|
-
return validateLease(JSON.parse(await readFile(path, 'utf8')));
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') return undefined;
|
|
189
|
-
throw new Blocked('Android lease state is malformed.');
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function validateLease(value) {
|
|
194
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('invalid lease');
|
|
195
|
-
const keys = ['schema', 'version', 'status', 'proofId', 'token', 'serial', 'appId', 'ownerPid', 'appPid', 'acquiredAt', 'expiresAt', 'updatedAt'].sort();
|
|
196
|
-
const actual = Object.keys(value).sort();
|
|
197
|
-
if (actual.length !== keys.length || actual.some((key, index) => key !== keys[index])) throw new Error('invalid lease keys');
|
|
198
|
-
if (value.schema !== 'codex-orchestrator.android-lease' || value.version !== 1 || !['active', 'released'].includes(value.status)) throw new Error('invalid lease schema');
|
|
199
|
-
requiredString(value.proofId, 'proof ID');
|
|
200
|
-
requiredString(value.token, 'token');
|
|
201
|
-
if (!/^emulator-[0-9]+$/u.test(value.serial)) throw new Error('invalid serial');
|
|
202
|
-
requiredAppId(value.appId);
|
|
203
|
-
requiredPid(value.ownerPid);
|
|
204
|
-
if (value.appPid !== null) requiredPid(value.appPid);
|
|
205
|
-
for (const field of ['acquiredAt', 'expiresAt', 'updatedAt']) {
|
|
206
|
-
if (new Date(value[field]).toISOString() !== value[field]) throw new Error('invalid lease timestamp');
|
|
207
|
-
}
|
|
208
|
-
return value;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function ensureDirectDirectory(path) {
|
|
212
|
-
const absolute = resolve(path);
|
|
213
|
-
await mkdir(absolute, { recursive: true, mode: 0o700 });
|
|
214
|
-
const info = await lstat(absolute);
|
|
215
|
-
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error('unsafe lease directory');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function createExclusiveJson(path, value) {
|
|
219
|
-
const handle = await open(path, 'wx', 0o600);
|
|
220
|
-
try {
|
|
221
|
-
await handle.writeFile(`${JSON.stringify(value)}\n`);
|
|
222
|
-
await handle.sync();
|
|
223
|
-
} finally {
|
|
224
|
-
await handle.close();
|
|
225
|
-
}
|
|
226
|
-
await syncDirectory(dirname(path));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async function replaceJson(path, value) {
|
|
230
|
-
await writeAtomicJson(path, value);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function writeAtomicJson(path, value) {
|
|
234
|
-
const temp = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
235
|
-
const handle = await open(temp, 'wx', 0o600);
|
|
236
|
-
try {
|
|
237
|
-
await handle.writeFile(`${JSON.stringify(value)}\n`);
|
|
238
|
-
await handle.sync();
|
|
239
|
-
} finally {
|
|
240
|
-
await handle.close();
|
|
241
|
-
}
|
|
242
|
-
await rename(temp, path);
|
|
243
|
-
await syncDirectory(dirname(path));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function syncDirectory(path) {
|
|
247
|
-
const handle = await open(path, 'r');
|
|
248
|
-
try { await handle.sync(); } finally { await handle.close(); }
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function publicResult(status, lease) {
|
|
252
|
-
return { status, proofId: lease.proofId, token: lease.token, serial: lease.serial, appId: lease.appId, appPid: lease.appPid, expiresAt: lease.expiresAt };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function requiredPath(value, field) {
|
|
256
|
-
const text = requiredString(value, field);
|
|
257
|
-
if (!text.startsWith('/')) throw new Blocked(`${field} must be absolute.`);
|
|
258
|
-
return resolve(text);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function requiredString(value, field) {
|
|
262
|
-
if (typeof value !== 'string' || value.length === 0 || value.length > 4096) throw new Blocked(`${field} is invalid.`);
|
|
263
|
-
return value;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function requiredAppId(value) {
|
|
267
|
-
const text = requiredString(value, 'app ID');
|
|
268
|
-
if (!/^[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)+$/u.test(text)) throw new Blocked('Android app ID is invalid.');
|
|
269
|
-
return text;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function requiredPid(value) {
|
|
273
|
-
const pid = Number(value);
|
|
274
|
-
if (!Number.isSafeInteger(pid) || pid < 1) throw new Blocked('Android owner PID is invalid.');
|
|
275
|
-
return pid;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function pidAlive(pid) {
|
|
279
|
-
try { process.kill(pid, 0); return true; } catch (error) { return error?.code === 'EPERM'; }
|
|
280
|
-
}
|