@kbediako/codex-orchestrator 0.1.12 → 0.1.14-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +19 -5
  2. package/README.md +47 -2
  3. package/dist/bin/codex-orchestrator.js +93 -0
  4. package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +27 -3
  5. package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +17 -1
  6. package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +36 -1
  7. package/dist/orchestrator/src/cli/adapters/CommandTester.js +28 -0
  8. package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +45 -0
  9. package/dist/orchestrator/src/cli/codexCliSetup.js +294 -0
  10. package/dist/orchestrator/src/cli/init.js +3 -0
  11. package/dist/orchestrator/src/cli/mcp.js +4 -2
  12. package/dist/orchestrator/src/cli/orchestrator.js +298 -28
  13. package/dist/orchestrator/src/cli/rlm/context.js +31 -3
  14. package/dist/orchestrator/src/cli/rlm/symbolic.js +152 -15
  15. package/dist/orchestrator/src/cli/rlmRunner.js +59 -5
  16. package/dist/orchestrator/src/cli/run/manifest.js +3 -0
  17. package/dist/orchestrator/src/cli/services/commandRunner.js +87 -0
  18. package/dist/orchestrator/src/cli/services/runSummaryWriter.js +24 -0
  19. package/dist/orchestrator/src/cli/skills.js +1 -1
  20. package/dist/orchestrator/src/cli/utils/codexCli.js +94 -0
  21. package/dist/orchestrator/src/cli/utils/codexPaths.js +13 -0
  22. package/dist/orchestrator/src/cli/utils/devtools.js +9 -12
  23. package/dist/orchestrator/src/cloud/CodexCloudTaskExecutor.js +255 -0
  24. package/dist/orchestrator/src/learning/crystalizer.js +2 -1
  25. package/dist/orchestrator/src/manager.js +1 -0
  26. package/dist/orchestrator/src/sync/CloudSyncWorker.js +37 -7
  27. package/dist/scripts/design/pipeline/context.js +3 -2
  28. package/dist/scripts/lib/run-manifests.js +14 -0
  29. package/docs/README.md +22 -2
  30. package/package.json +6 -2
  31. package/schemas/manifest.json +83 -0
  32. package/skills/collab-deliberation/SKILL.md +21 -0
  33. package/skills/collab-evals/SKILL.md +32 -0
  34. package/skills/delegate-early/SKILL.md +47 -0
  35. package/skills/delegation-usage/DELEGATION_GUIDE.md +5 -4
  36. package/skills/delegation-usage/SKILL.md +11 -5
  37. package/skills/docs-first/SKILL.md +2 -1
  38. package/templates/README.md +4 -0
@@ -0,0 +1,294 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { createReadStream, createWriteStream, existsSync } from 'node:fs';
4
+ import { chmod, copyFile, mkdir, rename, rm, writeFile } from 'node:fs/promises';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import process from 'node:process';
7
+ import { Readable } from 'node:stream';
8
+ import { pipeline } from 'node:stream/promises';
9
+ import { resolveCodexCliBinPath, resolveCodexCliConfigPath, resolveCodexCliReadiness, resolveCodexCliRoot } from './utils/codexCli.js';
10
+ import { resolveCodexHome } from './utils/codexPaths.js';
11
+ export async function runCodexCliSetup(options = {}) {
12
+ const env = options.env ?? process.env;
13
+ const plan = buildCodexCliSetupPlan(options, env);
14
+ const readiness = resolveCodexCliReadiness(env);
15
+ if (!options.apply) {
16
+ return { status: 'planned', plan, readiness, install: readiness.install ?? null };
17
+ }
18
+ if (readiness.status === 'ok' && !options.force) {
19
+ return {
20
+ status: 'skipped',
21
+ reason: 'CO-managed Codex CLI already installed.',
22
+ plan,
23
+ readiness,
24
+ install: readiness.install ?? null
25
+ };
26
+ }
27
+ if (readiness.config.status === 'invalid' && !options.force) {
28
+ throw new Error(`codex-cli config is invalid: ${readiness.config.path}`);
29
+ }
30
+ if (plan.method === 'download') {
31
+ if (!plan.downloadUrl) {
32
+ throw new Error('codex setup requires --download-url (or CODEX_CLI_DOWNLOAD_URL) when using download mode.');
33
+ }
34
+ if (!plan.downloadSha256) {
35
+ throw new Error('codex setup requires --download-sha256 (or CODEX_CLI_DOWNLOAD_SHA256) for download mode.');
36
+ }
37
+ await downloadCodexCli(plan.downloadUrl, plan.binPath, plan.downloadSha256);
38
+ }
39
+ else {
40
+ if (!plan.source) {
41
+ throw new Error('codex setup requires --source (or CODEX_CLI_SOURCE) when using build mode.');
42
+ }
43
+ const workspace = await prepareSource(plan.source, plan.ref, plan.installRoot);
44
+ try {
45
+ await buildCodexCli(workspace);
46
+ }
47
+ catch (error) {
48
+ throw formatCargoMissingHint(error);
49
+ }
50
+ const builtBinary = join(workspace, 'target', 'release', codexBinaryName());
51
+ if (!existsSync(builtBinary)) {
52
+ throw new Error(`codex CLI binary not found at ${builtBinary}`);
53
+ }
54
+ await ensureParentDir(plan.binPath);
55
+ await copyFile(builtBinary, plan.binPath);
56
+ await chmod(plan.binPath, 0o755);
57
+ }
58
+ const install = await writeCodexCliConfig(plan, env);
59
+ const updatedReadiness = resolveCodexCliReadiness(env);
60
+ return { status: 'applied', plan, readiness: updatedReadiness, install };
61
+ }
62
+ export function formatCodexCliSetupSummary(result) {
63
+ const lines = [];
64
+ lines.push(`Codex CLI setup: ${result.status}`);
65
+ if (result.reason) {
66
+ lines.push(`Note: ${result.reason}`);
67
+ }
68
+ lines.push(`- Codex home: ${result.plan.codexHome}`);
69
+ lines.push(`- Install root: ${result.plan.installRoot}`);
70
+ lines.push(`- Binary: ${result.plan.binPath}`);
71
+ lines.push(`- Config: ${result.plan.configPath}`);
72
+ lines.push(`- Method: ${result.plan.method}`);
73
+ if (result.plan.source) {
74
+ lines.push(`- Source: ${result.plan.source}`);
75
+ }
76
+ if (result.plan.ref) {
77
+ lines.push(`- Ref: ${result.plan.ref}`);
78
+ }
79
+ if (result.plan.downloadUrl) {
80
+ lines.push(`- Download URL: ${result.plan.downloadUrl}`);
81
+ }
82
+ if (result.plan.downloadSha256) {
83
+ lines.push(`- Download SHA256: ${result.plan.downloadSha256}`);
84
+ }
85
+ if (result.install?.sha256) {
86
+ lines.push(`- Installed SHA256: ${result.install.sha256}`);
87
+ }
88
+ lines.push(`- Command: ${result.plan.commandLine}`);
89
+ if (result.status === 'planned') {
90
+ lines.push('Run with --yes to apply this setup.');
91
+ }
92
+ return lines;
93
+ }
94
+ function buildCodexCliSetupPlan(options, env) {
95
+ const codexHome = resolveCodexHome(env);
96
+ const installRoot = resolveCodexCliRoot(env);
97
+ const binPath = resolveCodexCliBinPath(env);
98
+ const configPath = resolveCodexCliConfigPath(env);
99
+ const source = firstNonEmpty(options.source, env.CODEX_CLI_SOURCE);
100
+ const ref = firstNonEmpty(options.ref, env.CODEX_CLI_REF);
101
+ const downloadUrl = firstNonEmpty(options.downloadUrl, env.CODEX_CLI_DOWNLOAD_URL);
102
+ const downloadSha256 = firstNonEmpty(options.downloadSha256, env.CODEX_CLI_DOWNLOAD_SHA256);
103
+ const method = downloadUrl ? 'download' : 'build';
104
+ const commandLine = method === 'download'
105
+ ? `download ${downloadUrl ?? '<download-url>'} (sha256: ${downloadSha256 ?? '<sha256>'})`
106
+ : `cargo build -p codex-cli --release (cwd: ${source ?? '<source>'})`;
107
+ return {
108
+ codexHome,
109
+ installRoot,
110
+ binPath,
111
+ configPath,
112
+ method,
113
+ source,
114
+ ref,
115
+ downloadUrl,
116
+ downloadSha256,
117
+ commandLine
118
+ };
119
+ }
120
+ async function downloadCodexCli(url, destination, expectedSha256) {
121
+ await ensureParentDir(destination);
122
+ const response = await fetch(url);
123
+ if (!response.ok) {
124
+ throw new Error(`Failed to download codex CLI: ${response.status} ${response.statusText}`);
125
+ }
126
+ const body = response.body;
127
+ if (!body) {
128
+ throw new Error('Download response has no body.');
129
+ }
130
+ const tempPath = `${destination}.tmp`;
131
+ try {
132
+ await pipeline(Readable.fromWeb(body), createWriteStream(tempPath));
133
+ await chmod(tempPath, 0o755);
134
+ const actualSha256 = await sha256File(tempPath);
135
+ if (actualSha256 !== expectedSha256) {
136
+ throw new Error(`codex CLI sha256 mismatch: expected ${expectedSha256}, got ${actualSha256}`);
137
+ }
138
+ await rename(tempPath, destination);
139
+ }
140
+ catch (error) {
141
+ await rm(tempPath, { force: true });
142
+ throw error;
143
+ }
144
+ }
145
+ async function prepareSource(source, ref, root) {
146
+ const resolved = resolve(source);
147
+ if (existsSync(resolved)) {
148
+ const workspace = await resolveWorkspace(resolved);
149
+ if (!workspace) {
150
+ throw new Error(`codex source missing Cargo workspace at ${resolved}`);
151
+ }
152
+ return workspace;
153
+ }
154
+ const cloneDir = join(root, 'src');
155
+ if (!existsSync(cloneDir)) {
156
+ await runCommand('git', ['clone', source, cloneDir]);
157
+ }
158
+ else {
159
+ await runCommand('git', ['-C', cloneDir, 'fetch', '--all', '--prune']);
160
+ }
161
+ if (ref) {
162
+ await runCommand('git', ['-C', cloneDir, 'checkout', ref]);
163
+ }
164
+ const workspace = await resolveWorkspace(cloneDir);
165
+ if (!workspace) {
166
+ throw new Error(`codex source missing Cargo workspace at ${cloneDir}`);
167
+ }
168
+ return workspace;
169
+ }
170
+ async function resolveWorkspace(sourceRoot) {
171
+ const nested = join(sourceRoot, 'codex-rs', 'Cargo.toml');
172
+ if (existsSync(nested)) {
173
+ return join(sourceRoot, 'codex-rs');
174
+ }
175
+ const rootCargo = join(sourceRoot, 'Cargo.toml');
176
+ if (existsSync(rootCargo)) {
177
+ return sourceRoot;
178
+ }
179
+ return null;
180
+ }
181
+ async function buildCodexCli(workspace) {
182
+ await runCommand('cargo', ['build', '-p', 'codex-cli', '--release'], { cwd: workspace });
183
+ }
184
+ async function runCommand(command, args, options = {}) {
185
+ await new Promise((resolvePromise, reject) => {
186
+ const env = { ...process.env };
187
+ if (process.stdin?.isTTY !== true) {
188
+ env.GIT_TERMINAL_PROMPT = env.GIT_TERMINAL_PROMPT ?? '0';
189
+ env.GIT_ASKPASS = env.GIT_ASKPASS ?? 'echo';
190
+ env.GCM_INTERACTIVE = env.GCM_INTERACTIVE ?? 'never';
191
+ }
192
+ const child = spawn(command, args, { stdio: 'inherit', cwd: options.cwd, env });
193
+ child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
194
+ child.once('exit', (code) => {
195
+ if (code === 0) {
196
+ resolvePromise();
197
+ }
198
+ else {
199
+ reject(new Error(`${command} exited with code ${code ?? 'unknown'}`));
200
+ }
201
+ });
202
+ });
203
+ }
204
+ async function writeCodexCliConfig(plan, env) {
205
+ const config = {
206
+ binary_path: plan.binPath,
207
+ method: plan.method,
208
+ source: plan.source,
209
+ ref: plan.ref,
210
+ installed_at: new Date().toISOString()
211
+ };
212
+ if (plan.downloadUrl) {
213
+ config.source = plan.downloadUrl;
214
+ }
215
+ const sha256 = await sha256File(plan.binPath);
216
+ config.sha256 = sha256;
217
+ const version = await probeCodexCliVersion(plan.binPath, env);
218
+ if (version) {
219
+ config.version = version;
220
+ }
221
+ await ensureParentDir(plan.configPath);
222
+ await writeFile(plan.configPath, JSON.stringify(config, null, 2));
223
+ return config;
224
+ }
225
+ async function probeCodexCliVersion(binaryPath, env) {
226
+ try {
227
+ const output = await execCommand(binaryPath, ['--version'], env);
228
+ const trimmed = output.trim();
229
+ return trimmed.length > 0 ? trimmed : undefined;
230
+ }
231
+ catch {
232
+ return undefined;
233
+ }
234
+ }
235
+ async function execCommand(command, args, env) {
236
+ return await new Promise((resolvePromise, reject) => {
237
+ const child = spawn(command, args, { env, stdio: ['ignore', 'pipe', 'pipe'] });
238
+ let stdout = '';
239
+ let stderr = '';
240
+ child.stdout?.on('data', (chunk) => {
241
+ stdout += String(chunk);
242
+ });
243
+ child.stderr?.on('data', (chunk) => {
244
+ stderr += String(chunk);
245
+ });
246
+ child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
247
+ child.once('exit', (code) => {
248
+ if (code === 0) {
249
+ resolvePromise(stdout);
250
+ }
251
+ else {
252
+ reject(new Error(stderr || `${command} exited with code ${code ?? 'unknown'}`));
253
+ }
254
+ });
255
+ });
256
+ }
257
+ async function ensureParentDir(path) {
258
+ await mkdir(dirname(path), { recursive: true });
259
+ }
260
+ async function sha256File(path) {
261
+ return await new Promise((resolvePromise, reject) => {
262
+ const hash = createHash('sha256');
263
+ const stream = createReadStream(path);
264
+ stream.on('data', (chunk) => hash.update(chunk));
265
+ stream.on('end', () => resolvePromise(hash.digest('hex')));
266
+ stream.on('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
267
+ });
268
+ }
269
+ function codexBinaryName() {
270
+ return process.platform === 'win32' ? 'codex.exe' : 'codex';
271
+ }
272
+ function formatCargoMissingHint(error) {
273
+ const err = error instanceof Error ? error : new Error(String(error));
274
+ const message = err.message ?? '';
275
+ if (isCargoMissingError(err, message)) {
276
+ return new Error('cargo was not found in PATH. If you installed via rustup, add "$HOME/.cargo/bin" to PATH and retry.');
277
+ }
278
+ return err;
279
+ }
280
+ function isCargoMissingError(error, message) {
281
+ const err = error;
282
+ if (err.code === 'ENOENT' && message.includes('cargo')) {
283
+ return true;
284
+ }
285
+ return message.includes('spawn cargo ENOENT');
286
+ }
287
+ function firstNonEmpty(...values) {
288
+ for (const value of values) {
289
+ if (value && value.trim()) {
290
+ return value.trim();
291
+ }
292
+ }
293
+ return undefined;
294
+ }
@@ -60,5 +60,8 @@ export function formatInitSummary(result, cwd) {
60
60
  if (lines.length === 0) {
61
61
  lines.push('No files written.');
62
62
  }
63
+ lines.push('Next steps (recommended):');
64
+ lines.push(` - codex mcp add delegation -- codex-orchestrator delegate-server --repo ${cwd}`);
65
+ lines.push(' - codex-orchestrator codex setup # optional: CO-managed Codex CLI for collab JSONL');
63
66
  return lines;
64
67
  }
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import process from 'node:process';
5
5
  import { logger } from '../logger.js';
6
+ import { resolveCodexCliBin } from './utils/codexCli.js';
6
7
  const MCP_HEADER_TOKEN = 'Content-Length:';
7
8
  const MCP_HEADER_DELIMITER = '\r\n\r\n';
8
9
  export async function serveMcp(options) {
@@ -12,11 +13,12 @@ export async function serveMcp(options) {
12
13
  }
13
14
  if (options.dryRun) {
14
15
  logger.warn(`[mcp] repo root: ${repoRoot}`);
15
- logger.warn('[mcp] codex CLI must be available in PATH.');
16
+ logger.warn(`[mcp] codex CLI must be available at: ${resolveCodexCliBin()}`);
16
17
  return;
17
18
  }
18
19
  const args = ['-C', repoRoot, 'mcp-server', ...options.extraArgs];
19
- const child = spawn('codex', args, { stdio: ['inherit', 'pipe', 'pipe'] });
20
+ const command = resolveCodexCliBin();
21
+ const child = spawn(command, args, { stdio: ['inherit', 'pipe', 'pipe'] });
20
22
  if (child.stdout) {
21
23
  if (isStrictMcpStdout()) {
22
24
  attachMcpStdoutGuard(child.stdout);