@kbediako/codex-orchestrator 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/dist/bin/codex-orchestrator.js +238 -6
- package/dist/orchestrator/src/cli/delegationSetup.js +239 -0
- package/dist/orchestrator/src/cli/devtoolsSetup.js +2 -1
- package/dist/orchestrator/src/cli/doctor.js +4 -1
- package/dist/orchestrator/src/cli/doctorUsage.js +210 -0
- package/dist/orchestrator/src/cli/init.js +1 -1
- package/dist/orchestrator/src/cli/skills.js +6 -0
- package/docs/README.md +3 -1
- package/package.json +1 -1
- package/skills/chrome-devtools/SKILL.md +34 -0
- package/skills/delegation-usage/DELEGATION_GUIDE.md +2 -2
- package/skills/delegation-usage/SKILL.md +8 -4
package/README.md
CHANGED
|
@@ -130,6 +130,11 @@ flowchart TB
|
|
|
130
130
|
|
|
131
131
|
## Skills (bundled)
|
|
132
132
|
|
|
133
|
+
Recommended one-shot bootstrap (skills + delegation + DevTools wiring):
|
|
134
|
+
```bash
|
|
135
|
+
codex-orchestrator setup --yes
|
|
136
|
+
```
|
|
137
|
+
|
|
133
138
|
The release ships skills under `skills/` for downstream packaging. If you already have global skills installed, treat those as the primary reference and use bundled skills as the shipped fallback. Install bundled skills into `$CODEX_HOME/skills`:
|
|
134
139
|
```bash
|
|
135
140
|
codex-orchestrator skills install
|
|
@@ -142,6 +147,7 @@ Options:
|
|
|
142
147
|
|
|
143
148
|
Bundled skills (may vary by release):
|
|
144
149
|
- `collab-subagents-first`
|
|
150
|
+
- `chrome-devtools`
|
|
145
151
|
- `delegation-usage`
|
|
146
152
|
- `standalone-review`
|
|
147
153
|
- `docs-first`
|
|
@@ -152,11 +158,21 @@ Bundled skills (may vary by release):
|
|
|
152
158
|
|
|
153
159
|
## DevTools readiness
|
|
154
160
|
|
|
155
|
-
Check
|
|
161
|
+
Check readiness (deps + capability wiring):
|
|
156
162
|
```bash
|
|
157
163
|
codex-orchestrator doctor --format json
|
|
158
164
|
```
|
|
159
165
|
|
|
166
|
+
Auto-fix wiring (delegation + DevTools):
|
|
167
|
+
```bash
|
|
168
|
+
codex-orchestrator doctor --apply --yes
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Usage snapshot (scans local `.runs/`):
|
|
172
|
+
```bash
|
|
173
|
+
codex-orchestrator doctor --usage
|
|
174
|
+
```
|
|
175
|
+
|
|
160
176
|
Print DevTools MCP setup guidance:
|
|
161
177
|
```bash
|
|
162
178
|
codex-orchestrator devtools setup
|
|
@@ -168,9 +184,11 @@ codex-orchestrator devtools setup
|
|
|
168
184
|
- `codex-orchestrator plan <pipeline>` — preview pipeline stages.
|
|
169
185
|
- `codex-orchestrator exec <cmd>` — run a one-off command with the exec runtime.
|
|
170
186
|
- `codex-orchestrator init codex` — install starter templates (`mcp-client.json`, `AGENTS.md`) into a repo.
|
|
187
|
+
- `codex-orchestrator setup --yes` — install bundled skills and configure delegation + DevTools wiring.
|
|
171
188
|
- `codex-orchestrator init codex --codex-cli --yes --codex-source <path>` — optionally provision a CO-managed Codex CLI binary (build-from-source default; set `CODEX_CLI_SOURCE` to avoid passing `--codex-source` every time).
|
|
172
189
|
- `codex-orchestrator init codex --codex-cli --yes --codex-download-url <url> --codex-download-sha256 <sha>` — opt-in to a prebuilt Codex CLI download.
|
|
173
190
|
- `codex-orchestrator codex setup` — plan/apply a CO-managed Codex CLI install (optional managed/pinned path; use `--download-url` + `--download-sha256` for prebuilts).
|
|
191
|
+
- `codex-orchestrator delegation setup --yes` — configure delegation MCP server wiring.
|
|
174
192
|
- `codex-orchestrator self-check --format json` — JSON health payload.
|
|
175
193
|
- `codex-orchestrator mcp serve` — Codex MCP stdio server.
|
|
176
194
|
|
|
@@ -14,9 +14,11 @@ import { evaluateInteractiveGate } from '../orchestrator/src/cli/utils/interacti
|
|
|
14
14
|
import { buildSelfCheckResult } from '../orchestrator/src/cli/selfCheck.js';
|
|
15
15
|
import { initCodexTemplates, formatInitSummary } from '../orchestrator/src/cli/init.js';
|
|
16
16
|
import { runDoctor, formatDoctorSummary } from '../orchestrator/src/cli/doctor.js';
|
|
17
|
+
import { formatDoctorUsageSummary, runDoctorUsage } from '../orchestrator/src/cli/doctorUsage.js';
|
|
17
18
|
import { formatDevtoolsSetupSummary, runDevtoolsSetup } from '../orchestrator/src/cli/devtoolsSetup.js';
|
|
18
19
|
import { formatCodexCliSetupSummary, runCodexCliSetup } from '../orchestrator/src/cli/codexCliSetup.js';
|
|
19
|
-
import {
|
|
20
|
+
import { formatDelegationSetupSummary, runDelegationSetup } from '../orchestrator/src/cli/delegationSetup.js';
|
|
21
|
+
import { formatSkillsInstallSummary, installSkills, listBundledSkills } from '../orchestrator/src/cli/skills.js';
|
|
20
22
|
import { loadPackageInfo } from '../orchestrator/src/cli/utils/packageInfo.js';
|
|
21
23
|
import { slugify } from '../orchestrator/src/cli/utils/strings.js';
|
|
22
24
|
import { serveMcp } from '../orchestrator/src/cli/mcp.js';
|
|
@@ -63,6 +65,9 @@ async function main() {
|
|
|
63
65
|
case 'init':
|
|
64
66
|
await handleInit(args);
|
|
65
67
|
break;
|
|
68
|
+
case 'setup':
|
|
69
|
+
await handleSetup(args);
|
|
70
|
+
break;
|
|
66
71
|
case 'doctor':
|
|
67
72
|
await handleDoctor(args);
|
|
68
73
|
break;
|
|
@@ -85,6 +90,9 @@ async function main() {
|
|
|
85
90
|
case 'delegation-server':
|
|
86
91
|
await handleDelegationServer(args);
|
|
87
92
|
break;
|
|
93
|
+
case 'delegation':
|
|
94
|
+
await handleDelegation(args);
|
|
95
|
+
break;
|
|
88
96
|
case 'version':
|
|
89
97
|
printVersion();
|
|
90
98
|
break;
|
|
@@ -332,6 +340,16 @@ async function handleRlm(orchestrator, rawArgs) {
|
|
|
332
340
|
process.env.MCP_RUNNER_TASK_ID = taskId;
|
|
333
341
|
applyRlmEnvOverrides(flags, goal);
|
|
334
342
|
console.log(`Task: ${taskId}`);
|
|
343
|
+
const collabUserChoice = flags['collab'] !== undefined || process.env.RLM_SYMBOLIC_COLLAB !== undefined;
|
|
344
|
+
if (!collabUserChoice) {
|
|
345
|
+
const doctor = runDoctor();
|
|
346
|
+
if (doctor.collab.status === 'ok') {
|
|
347
|
+
console.log('Tip: collab is enabled. Try: codex-orchestrator rlm --collab auto \"<goal>\"');
|
|
348
|
+
}
|
|
349
|
+
else if (doctor.collab.status === 'disabled') {
|
|
350
|
+
console.log('Tip: collab is available but disabled. Enable with: codex features enable collab');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
335
353
|
let startResult = null;
|
|
336
354
|
await withRunUi(flags, 'text', async (runEvents) => {
|
|
337
355
|
startResult = await orchestrator.start({
|
|
@@ -541,16 +559,192 @@ async function handleInit(rawArgs) {
|
|
|
541
559
|
}
|
|
542
560
|
}
|
|
543
561
|
}
|
|
562
|
+
async function handleSetup(rawArgs) {
|
|
563
|
+
const { positionals, flags } = parseArgs(rawArgs);
|
|
564
|
+
if (isHelpRequest(positionals, flags)) {
|
|
565
|
+
console.log(`Usage: codex-orchestrator setup [--yes] [--format json]
|
|
566
|
+
|
|
567
|
+
One-shot bootstrap for downstream users. Installs bundled skills and configures
|
|
568
|
+
delegation + DevTools MCP wiring.
|
|
569
|
+
|
|
570
|
+
Options:
|
|
571
|
+
--yes Apply setup (otherwise plan only).
|
|
572
|
+
--repo <path> Repo root for delegation wiring (default cwd).
|
|
573
|
+
--format json Emit machine-readable output (dry-run only).
|
|
574
|
+
`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const format = flags['format'] === 'json' ? 'json' : 'text';
|
|
578
|
+
const apply = Boolean(flags['yes']);
|
|
579
|
+
if (format === 'json' && apply) {
|
|
580
|
+
throw new Error('setup does not support --format json with --yes.');
|
|
581
|
+
}
|
|
582
|
+
const repoFlag = readStringFlag(flags, 'repo');
|
|
583
|
+
const repoRoot = repoFlag ?? process.cwd();
|
|
584
|
+
const repoFlagValue = repoFlag ? (/\s/u.test(repoFlag) ? JSON.stringify(repoFlag) : repoFlag) : null;
|
|
585
|
+
const delegationRepoArg = repoFlagValue ? ` --repo ${repoFlagValue}` : '';
|
|
586
|
+
const bundledSkills = await listBundledSkills();
|
|
587
|
+
if (bundledSkills.length === 0) {
|
|
588
|
+
throw new Error('No bundled skills detected; cannot run setup.');
|
|
589
|
+
}
|
|
590
|
+
const forceSkills = bundledSkills.filter((skill) => skill !== 'chrome-devtools');
|
|
591
|
+
if (!apply) {
|
|
592
|
+
const forceOnly = forceSkills.join(',');
|
|
593
|
+
const forceCommand = forceOnly ? `codex-orchestrator skills install --force --only ${forceOnly}` : null;
|
|
594
|
+
const devtoolsCommand = bundledSkills.includes('chrome-devtools')
|
|
595
|
+
? 'codex-orchestrator skills install --only chrome-devtools'
|
|
596
|
+
: null;
|
|
597
|
+
const delegation = await runDelegationSetup({ repoRoot });
|
|
598
|
+
const devtools = await runDevtoolsSetup();
|
|
599
|
+
const payload = {
|
|
600
|
+
status: 'planned',
|
|
601
|
+
steps: {
|
|
602
|
+
skills: {
|
|
603
|
+
commandLines: [forceCommand, devtoolsCommand].filter((entry) => Boolean(entry)),
|
|
604
|
+
note: 'Installs bundled skills into $CODEX_HOME/skills (setup avoids overwriting chrome-devtools when already present).'
|
|
605
|
+
},
|
|
606
|
+
delegation,
|
|
607
|
+
devtools
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
if (format === 'json') {
|
|
611
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
console.log('Setup plan:');
|
|
615
|
+
console.log('- Skills:');
|
|
616
|
+
for (const commandLine of payload.steps.skills.commandLines) {
|
|
617
|
+
console.log(` - ${commandLine}`);
|
|
618
|
+
}
|
|
619
|
+
console.log(`- Delegation: codex-orchestrator delegation setup --yes${delegationRepoArg}`);
|
|
620
|
+
console.log('- DevTools: codex-orchestrator devtools setup --yes');
|
|
621
|
+
console.log('Run with --yes to apply this setup.');
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const primarySkills = forceSkills.length > 0 ? await installSkills({ force: true, only: forceSkills }) : null;
|
|
625
|
+
const devtoolsSkill = bundledSkills.includes('chrome-devtools')
|
|
626
|
+
? await installSkills({ force: false, only: ['chrome-devtools'] })
|
|
627
|
+
: null;
|
|
628
|
+
const skills = primarySkills && devtoolsSkill
|
|
629
|
+
? {
|
|
630
|
+
sourceRoot: primarySkills.sourceRoot,
|
|
631
|
+
targetRoot: primarySkills.targetRoot,
|
|
632
|
+
skills: [...new Set([...primarySkills.skills, ...devtoolsSkill.skills])],
|
|
633
|
+
written: [...primarySkills.written, ...devtoolsSkill.written],
|
|
634
|
+
skipped: [...primarySkills.skipped, ...devtoolsSkill.skipped]
|
|
635
|
+
}
|
|
636
|
+
: devtoolsSkill ?? primarySkills;
|
|
637
|
+
if (!skills) {
|
|
638
|
+
throw new Error('No bundled skills detected; cannot run setup.');
|
|
639
|
+
}
|
|
640
|
+
const delegation = await runDelegationSetup({ apply: true, repoRoot });
|
|
641
|
+
const devtools = await runDevtoolsSetup({ apply: true });
|
|
642
|
+
for (const line of formatSkillsInstallSummary(skills)) {
|
|
643
|
+
console.log(line);
|
|
644
|
+
}
|
|
645
|
+
for (const line of formatDelegationSetupSummary(delegation)) {
|
|
646
|
+
console.log(line);
|
|
647
|
+
}
|
|
648
|
+
for (const line of formatDevtoolsSetupSummary(devtools)) {
|
|
649
|
+
console.log(line);
|
|
650
|
+
}
|
|
651
|
+
console.log('Next: codex-orchestrator doctor --usage');
|
|
652
|
+
}
|
|
544
653
|
async function handleDoctor(rawArgs) {
|
|
545
654
|
const { flags } = parseArgs(rawArgs);
|
|
546
655
|
const format = flags['format'] === 'json' ? 'json' : 'text';
|
|
547
|
-
const
|
|
656
|
+
const includeUsage = Boolean(flags['usage']);
|
|
657
|
+
const wantsApply = Boolean(flags['apply']);
|
|
658
|
+
const apply = Boolean(flags['yes']);
|
|
659
|
+
if (wantsApply && format === 'json') {
|
|
660
|
+
throw new Error('doctor --apply does not support --format json.');
|
|
661
|
+
}
|
|
662
|
+
const windowDaysRaw = readStringFlag(flags, 'window-days');
|
|
663
|
+
let windowDays = undefined;
|
|
664
|
+
if (windowDaysRaw) {
|
|
665
|
+
if (!/^\d+$/u.test(windowDaysRaw)) {
|
|
666
|
+
throw new Error(`Invalid --window-days value '${windowDaysRaw}'. Expected a positive integer.`);
|
|
667
|
+
}
|
|
668
|
+
const parsed = Number(windowDaysRaw);
|
|
669
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
670
|
+
throw new Error(`Invalid --window-days value '${windowDaysRaw}'. Expected a positive integer.`);
|
|
671
|
+
}
|
|
672
|
+
windowDays = parsed;
|
|
673
|
+
}
|
|
674
|
+
const taskFilter = readStringFlag(flags, 'task') ?? null;
|
|
675
|
+
const doctorResult = runDoctor();
|
|
676
|
+
const usageResult = includeUsage ? await runDoctorUsage({ windowDays, taskFilter }) : null;
|
|
548
677
|
if (format === 'json') {
|
|
549
|
-
|
|
678
|
+
if (usageResult) {
|
|
679
|
+
console.log(JSON.stringify({ ...doctorResult, usage: usageResult }, null, 2));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
console.log(JSON.stringify(doctorResult, null, 2));
|
|
550
683
|
return;
|
|
551
684
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
685
|
+
for (const line of formatDoctorSummary(doctorResult)) {
|
|
686
|
+
console.log(line);
|
|
687
|
+
}
|
|
688
|
+
if (usageResult) {
|
|
689
|
+
for (const line of formatDoctorUsageSummary(usageResult)) {
|
|
690
|
+
console.log(line);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (!wantsApply) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const repoRoot = process.cwd();
|
|
697
|
+
const delegationPlan = await runDelegationSetup({ repoRoot });
|
|
698
|
+
const devtoolsPlan = await runDevtoolsSetup();
|
|
699
|
+
const needsDelegation = !delegationPlan.readiness.configured;
|
|
700
|
+
const needsDevtoolsSkill = devtoolsPlan.readiness.skill.status !== 'ok';
|
|
701
|
+
const devtoolsConfigStatus = devtoolsPlan.readiness.config.status;
|
|
702
|
+
const needsDevtoolsConfig = devtoolsConfigStatus === 'missing';
|
|
703
|
+
const hasInvalidDevtoolsConfig = devtoolsConfigStatus === 'invalid';
|
|
704
|
+
if (!needsDelegation && !needsDevtoolsSkill && !needsDevtoolsConfig && !hasInvalidDevtoolsConfig) {
|
|
705
|
+
console.log('Doctor apply: nothing to do.');
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
console.log('Doctor apply plan:');
|
|
709
|
+
if (needsDevtoolsSkill) {
|
|
710
|
+
console.log('- Install skill: chrome-devtools (codex-orchestrator skills install --only chrome-devtools)');
|
|
711
|
+
}
|
|
712
|
+
if (hasInvalidDevtoolsConfig) {
|
|
713
|
+
console.log(`- DevTools MCP config is invalid: ${devtoolsPlan.readiness.config.path} (fix config.toml then rerun doctor --apply)`);
|
|
714
|
+
}
|
|
715
|
+
if (needsDevtoolsConfig) {
|
|
716
|
+
console.log('- Configure DevTools MCP: codex-orchestrator devtools setup --yes');
|
|
717
|
+
}
|
|
718
|
+
if (needsDelegation) {
|
|
719
|
+
console.log('- Configure delegation MCP: codex-orchestrator delegation setup --yes');
|
|
720
|
+
}
|
|
721
|
+
if (!apply) {
|
|
722
|
+
console.log('Run with --apply --yes to apply these fixes.');
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (needsDevtoolsSkill) {
|
|
726
|
+
const skills = await installSkills({ only: ['chrome-devtools'] });
|
|
727
|
+
for (const line of formatSkillsInstallSummary(skills)) {
|
|
728
|
+
console.log(line);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (needsDelegation) {
|
|
732
|
+
const delegation = await runDelegationSetup({ apply: true, repoRoot });
|
|
733
|
+
for (const line of formatDelegationSetupSummary(delegation)) {
|
|
734
|
+
console.log(line);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (hasInvalidDevtoolsConfig) {
|
|
738
|
+
console.log(`DevTools setup: skipped (config.toml is invalid: ${devtoolsPlan.readiness.config.path}). Fix it and rerun doctor --apply --yes.`);
|
|
739
|
+
}
|
|
740
|
+
else if (needsDevtoolsConfig) {
|
|
741
|
+
const devtools = await runDevtoolsSetup({ apply: true });
|
|
742
|
+
for (const line of formatDevtoolsSetupSummary(devtools)) {
|
|
743
|
+
console.log(line);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const doctorAfter = runDoctor();
|
|
747
|
+
for (const line of formatDoctorSummary(doctorAfter)) {
|
|
554
748
|
console.log(line);
|
|
555
749
|
}
|
|
556
750
|
}
|
|
@@ -578,6 +772,30 @@ async function handleDevtools(rawArgs) {
|
|
|
578
772
|
console.log(line);
|
|
579
773
|
}
|
|
580
774
|
}
|
|
775
|
+
async function handleDelegation(rawArgs) {
|
|
776
|
+
const { positionals, flags } = parseArgs(rawArgs);
|
|
777
|
+
const subcommand = positionals.shift();
|
|
778
|
+
if (!subcommand) {
|
|
779
|
+
throw new Error('delegation requires a subcommand (setup).');
|
|
780
|
+
}
|
|
781
|
+
if (subcommand !== 'setup') {
|
|
782
|
+
throw new Error(`Unknown delegation subcommand: ${subcommand}`);
|
|
783
|
+
}
|
|
784
|
+
const format = flags['format'] === 'json' ? 'json' : 'text';
|
|
785
|
+
const apply = Boolean(flags['yes']);
|
|
786
|
+
if (format === 'json' && apply) {
|
|
787
|
+
throw new Error('delegation setup does not support --format json with --yes.');
|
|
788
|
+
}
|
|
789
|
+
const repoRoot = readStringFlag(flags, 'repo') ?? process.cwd();
|
|
790
|
+
const result = await runDelegationSetup({ apply, repoRoot });
|
|
791
|
+
if (format === 'json') {
|
|
792
|
+
console.log(JSON.stringify(result, null, 2));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
for (const line of formatDelegationSetupSummary(result)) {
|
|
796
|
+
console.log(line);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
581
799
|
async function handleCodex(rawArgs) {
|
|
582
800
|
const { positionals, flags } = parseArgs(rawArgs);
|
|
583
801
|
const subcommand = positionals.shift();
|
|
@@ -961,7 +1179,17 @@ Commands:
|
|
|
961
1179
|
--codex-download-sha256 <sha> Expected SHA256 for the prebuilt download.
|
|
962
1180
|
--codex-force Overwrite existing CO-managed codex binary.
|
|
963
1181
|
--yes Apply codex CLI setup (otherwise plan only).
|
|
964
|
-
|
|
1182
|
+
setup [--yes] [--format json]
|
|
1183
|
+
--yes Apply setup (otherwise plan only).
|
|
1184
|
+
--repo <path> Repo root for delegation wiring (default cwd).
|
|
1185
|
+
--format json Emit machine-readable output (dry-run only).
|
|
1186
|
+
doctor [--format json] [--usage] [--window-days <n>] [--task <id>] [--apply]
|
|
1187
|
+
--usage Include a local usage snapshot (scans .runs/).
|
|
1188
|
+
--window-days <n> Window for --usage (default 30).
|
|
1189
|
+
--task <id> Limit --usage scan to a specific task directory.
|
|
1190
|
+
--apply Plan/apply quick fixes for DevTools + delegation wiring (use with --yes).
|
|
1191
|
+
--yes Apply fixes when --apply is set.
|
|
1192
|
+
--format json Emit machine-readable output (not supported with --apply).
|
|
965
1193
|
codex setup
|
|
966
1194
|
--source <path> Build from local Codex repo (or git URL).
|
|
967
1195
|
--ref <ref> Git ref (branch/tag/sha) when building from repo.
|
|
@@ -973,6 +1201,10 @@ Commands:
|
|
|
973
1201
|
devtools setup Print DevTools MCP setup instructions.
|
|
974
1202
|
--yes Apply setup by running "codex mcp add ...".
|
|
975
1203
|
--format json Emit machine-readable output (dry-run only).
|
|
1204
|
+
delegation setup Configure delegation MCP server wiring.
|
|
1205
|
+
--repo <path> Repo root for delegation server (default cwd).
|
|
1206
|
+
--yes Apply setup by running "codex mcp add ...".
|
|
1207
|
+
--format json Emit machine-readable output (dry-run only).
|
|
976
1208
|
skills install Install bundled skills into $CODEX_HOME/skills.
|
|
977
1209
|
--force Overwrite existing skill files.
|
|
978
1210
|
--only <skills> Install only selected skills (comma-separated).
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { resolveCodexCliBin } from './utils/codexCli.js';
|
|
6
|
+
import { resolveCodexHome } from './utils/codexPaths.js';
|
|
7
|
+
export async function runDelegationSetup(options = {}) {
|
|
8
|
+
const env = options.env ?? process.env;
|
|
9
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
10
|
+
const codexBin = resolveCodexCliBin(env);
|
|
11
|
+
const codexHome = resolveCodexHome(env);
|
|
12
|
+
const configPath = join(codexHome, 'config.toml');
|
|
13
|
+
const plan = {
|
|
14
|
+
codexBin,
|
|
15
|
+
codexHome,
|
|
16
|
+
repoRoot,
|
|
17
|
+
commandLine: `"${codexBin}" mcp add delegation -- codex-orchestrator delegate-server`
|
|
18
|
+
};
|
|
19
|
+
const probe = inspectDelegationReadiness({ codexBin, configPath, repoRoot, env });
|
|
20
|
+
const readiness = { configured: probe.configured, configPath };
|
|
21
|
+
if (!options.apply) {
|
|
22
|
+
return { status: 'planned', plan, readiness };
|
|
23
|
+
}
|
|
24
|
+
if (probe.configured) {
|
|
25
|
+
return { status: 'skipped', reason: probe.reason ?? 'Delegation MCP is already configured.', plan, readiness };
|
|
26
|
+
}
|
|
27
|
+
await applyDelegationSetup({ codexBin, removeExisting: probe.removeExisting, envVars: probe.envVars }, env);
|
|
28
|
+
const configuredAfter = inspectDelegationReadiness({ codexBin, configPath, repoRoot, env }).configured;
|
|
29
|
+
return {
|
|
30
|
+
status: 'applied',
|
|
31
|
+
reason: probe.reason,
|
|
32
|
+
plan,
|
|
33
|
+
readiness: { ...readiness, configured: configuredAfter }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function formatDelegationSetupSummary(result) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
lines.push(`Delegation setup: ${result.status}`);
|
|
39
|
+
if (result.reason) {
|
|
40
|
+
lines.push(`Note: ${result.reason}`);
|
|
41
|
+
}
|
|
42
|
+
lines.push(`- Codex home: ${result.plan.codexHome}`);
|
|
43
|
+
lines.push(`- Config: ${result.readiness.configured ? 'ok' : 'missing'} (${result.readiness.configPath})`);
|
|
44
|
+
lines.push(`- Command: ${result.plan.commandLine}`);
|
|
45
|
+
if (result.status === 'planned') {
|
|
46
|
+
lines.push('Run with --yes to apply this setup.');
|
|
47
|
+
}
|
|
48
|
+
return lines;
|
|
49
|
+
}
|
|
50
|
+
function inspectDelegationReadiness(options) {
|
|
51
|
+
const requestedRepo = resolve(options.repoRoot);
|
|
52
|
+
const existing = readDelegationMcpServer(options.codexBin, options.env);
|
|
53
|
+
if (existing) {
|
|
54
|
+
const envVars = existing.envVars;
|
|
55
|
+
const isDelegationServer = existing.args.includes('delegate-server') || existing.args.includes('delegation-server');
|
|
56
|
+
if (!isDelegationServer) {
|
|
57
|
+
return {
|
|
58
|
+
configured: false,
|
|
59
|
+
removeExisting: true,
|
|
60
|
+
envVars,
|
|
61
|
+
reason: 'Existing delegation MCP entry does not point to codex-orchestrator delegate-server; reconfiguring.'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (existing.pinnedRepo) {
|
|
65
|
+
const pinnedRepo = resolve(existing.pinnedRepo);
|
|
66
|
+
if (pinnedRepo !== requestedRepo) {
|
|
67
|
+
return {
|
|
68
|
+
configured: false,
|
|
69
|
+
removeExisting: true,
|
|
70
|
+
envVars,
|
|
71
|
+
reason: `Existing delegation MCP entry is pinned to ${existing.pinnedRepo}; reconfiguring.`
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
configured: true,
|
|
76
|
+
removeExisting: false,
|
|
77
|
+
envVars,
|
|
78
|
+
reason: `Delegation MCP is already configured (pinned to ${existing.pinnedRepo}).`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
configured: true,
|
|
83
|
+
removeExisting: false,
|
|
84
|
+
envVars,
|
|
85
|
+
reason: 'Delegation MCP is already configured.'
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Fall back to directly scanning config.toml when the Codex CLI probe is unavailable.
|
|
89
|
+
const configured = isDelegationConfiguredFallback(options.configPath);
|
|
90
|
+
return { configured, removeExisting: false, envVars: {} };
|
|
91
|
+
}
|
|
92
|
+
function applyDelegationSetup(plan, env) {
|
|
93
|
+
const envFlags = [];
|
|
94
|
+
for (const [key, value] of Object.entries(plan.envVars ?? {})) {
|
|
95
|
+
envFlags.push('--env', `${key}=${value}`);
|
|
96
|
+
}
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const runAdd = () => {
|
|
99
|
+
const child = spawn(plan.codexBin, ['mcp', 'add', 'delegation', ...envFlags, '--', 'codex-orchestrator', 'delegate-server'], { stdio: 'inherit', env });
|
|
100
|
+
child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
|
|
101
|
+
child.once('exit', (code) => {
|
|
102
|
+
if (code === 0) {
|
|
103
|
+
resolve();
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
reject(new Error(`codex mcp add exited with code ${code ?? 'unknown'}`));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
if (!plan.removeExisting) {
|
|
111
|
+
runAdd();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const child = spawn(plan.codexBin, ['mcp', 'remove', 'delegation'], { stdio: 'inherit', env });
|
|
115
|
+
child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
|
|
116
|
+
child.once('exit', (code) => {
|
|
117
|
+
if (code === 0) {
|
|
118
|
+
runAdd();
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
reject(new Error(`codex mcp remove exited with code ${code ?? 'unknown'}`));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function readDelegationMcpServer(codexBin, env) {
|
|
127
|
+
const result = spawnSync(codexBin, ['mcp', 'get', 'delegation', '--json'], {
|
|
128
|
+
encoding: 'utf8',
|
|
129
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
130
|
+
timeout: 5000,
|
|
131
|
+
env
|
|
132
|
+
});
|
|
133
|
+
if (result.error || result.status !== 0) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const stdout = String(result.stdout ?? '').trim();
|
|
137
|
+
if (!stdout) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(stdout);
|
|
142
|
+
const transport = parsed.transport;
|
|
143
|
+
const args = Array.isArray(transport?.args)
|
|
144
|
+
? transport.args.filter((value) => typeof value === 'string')
|
|
145
|
+
: [];
|
|
146
|
+
const envVars = {};
|
|
147
|
+
const envRecord = transport?.env;
|
|
148
|
+
if (envRecord && typeof envRecord === 'object' && !Array.isArray(envRecord)) {
|
|
149
|
+
for (const [key, value] of Object.entries(envRecord)) {
|
|
150
|
+
if (typeof value === 'string') {
|
|
151
|
+
envVars[key] = value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const pinnedRepo = readPinnedRepo(args);
|
|
156
|
+
return { args, pinnedRepo, envVars };
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function readPinnedRepo(args) {
|
|
163
|
+
const index = args.indexOf('--repo');
|
|
164
|
+
if (index === -1) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const candidate = args[index + 1];
|
|
168
|
+
return typeof candidate === 'string' && candidate.trim().length > 0 ? candidate.trim() : null;
|
|
169
|
+
}
|
|
170
|
+
function isDelegationConfiguredFallback(configPath) {
|
|
171
|
+
if (!existsSync(configPath)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
// Keep parsing loose; we only need to know whether a delegation entry exists.
|
|
176
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
177
|
+
return hasMcpServerEntry(raw, 'delegation');
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function hasMcpServerEntry(raw, serverName) {
|
|
184
|
+
const lines = raw.split('\n');
|
|
185
|
+
let currentTable = null;
|
|
186
|
+
for (const line of lines) {
|
|
187
|
+
const trimmed = stripTomlComment(line).trim();
|
|
188
|
+
if (!trimmed) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const tableMatch = trimmed.match(/^\[(.+)\]$/u);
|
|
192
|
+
if (tableMatch) {
|
|
193
|
+
currentTable = tableMatch[1]?.trim() ?? null;
|
|
194
|
+
if (currentTable === `mcp_servers.${serverName}` ||
|
|
195
|
+
currentTable === `mcp_servers."${serverName}"` ||
|
|
196
|
+
currentTable === `mcp_servers.'${serverName}'`) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (trimmed.startsWith('mcp_servers.')) {
|
|
202
|
+
if (trimmed.startsWith(`mcp_servers."${serverName}".`)) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
if (trimmed.startsWith(`mcp_servers.'${serverName}'.`)) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
if (trimmed.startsWith(`mcp_servers.${serverName}.`)) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (trimmed.startsWith(`mcp_servers."${serverName}"=`)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (trimmed.startsWith(`mcp_servers.'${serverName}'=`)) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (trimmed.startsWith(`mcp_servers.${serverName}=`)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (currentTable === 'mcp_servers') {
|
|
222
|
+
const entryPattern = new RegExp(`^"?${escapeRegExp(serverName)}"?\\s*=`, 'u');
|
|
223
|
+
if (entryPattern.test(trimmed)) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
function stripTomlComment(line) {
|
|
231
|
+
const index = line.indexOf('#');
|
|
232
|
+
if (index === -1) {
|
|
233
|
+
return line;
|
|
234
|
+
}
|
|
235
|
+
return line.slice(0, index);
|
|
236
|
+
}
|
|
237
|
+
function escapeRegExp(value) {
|
|
238
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
239
|
+
}
|
|
@@ -20,7 +20,8 @@ export async function runDevtoolsSetup(options = {}) {
|
|
|
20
20
|
throw new Error(`Cannot apply DevTools setup because config.toml is invalid: ${readiness.config.path}`);
|
|
21
21
|
}
|
|
22
22
|
await applyDevtoolsSetup(plan, env);
|
|
23
|
-
|
|
23
|
+
const readinessAfter = resolveDevtoolsReadiness(env);
|
|
24
|
+
return { status: 'applied', plan, readiness: readinessAfter };
|
|
24
25
|
}
|
|
25
26
|
export function formatDevtoolsSetupSummary(result) {
|
|
26
27
|
const lines = [];
|
|
@@ -51,6 +51,7 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
51
51
|
install: readiness.config.status === 'ok'
|
|
52
52
|
? undefined
|
|
53
53
|
: [
|
|
54
|
+
'Quick fix: codex-orchestrator doctor --apply --yes',
|
|
54
55
|
'Run: codex-orchestrator devtools setup',
|
|
55
56
|
`Run: ${setupPlan.commandLine}`,
|
|
56
57
|
`Config path: ${setupPlan.configPath}`,
|
|
@@ -115,7 +116,9 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
115
116
|
status: delegationStatus,
|
|
116
117
|
config: delegationConfig,
|
|
117
118
|
enablement: [
|
|
118
|
-
|
|
119
|
+
'Quick fix: codex-orchestrator doctor --apply --yes',
|
|
120
|
+
'Run: codex-orchestrator delegation setup --yes',
|
|
121
|
+
'Or manually: codex mcp add delegation -- codex-orchestrator delegate-server',
|
|
119
122
|
"Enable for a run with: codex -c 'mcp_servers.delegation.enabled=true' ...",
|
|
120
123
|
'See: codex-orchestrator init codex'
|
|
121
124
|
]
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { collectManifests, findSubagentManifests, parseRunIdTimestamp, resolveEnvironmentPaths } from '../../../scripts/lib/run-manifests.js';
|
|
6
|
+
import { normalizeTaskKey as normalizeTaskKeyAny } from '../../../scripts/lib/docs-helpers.js';
|
|
7
|
+
const normalizeTaskKey = normalizeTaskKeyAny;
|
|
8
|
+
export async function runDoctorUsage(options = {}) {
|
|
9
|
+
const windowDays = clampInt(options.windowDays ?? 30, 1, 3650);
|
|
10
|
+
const cutoffMs = Date.now() - windowDays * 24 * 60 * 60 * 1000;
|
|
11
|
+
const cutoffIso = new Date(cutoffMs).toISOString();
|
|
12
|
+
const env = resolveEnvironmentPaths();
|
|
13
|
+
const manifestPaths = await collectManifests(env.runsRoot, options.taskFilter ?? undefined);
|
|
14
|
+
const seenRunIds = new Set();
|
|
15
|
+
const pipelines = new Map();
|
|
16
|
+
const cloudByStatus = {};
|
|
17
|
+
const statusCounts = { total: 0, succeeded: 0, failed: 0, cancelled: 0, other: 0 };
|
|
18
|
+
let cloudRuns = 0;
|
|
19
|
+
let rlmRuns = 0;
|
|
20
|
+
let collabRunsWithToolCalls = 0;
|
|
21
|
+
let collabTotalToolCalls = 0;
|
|
22
|
+
const collabCaptureDisabled = String(process.env.CODEX_ORCHESTRATOR_COLLAB_MAX_EVENTS ?? '').trim() === '0';
|
|
23
|
+
const activeIndexTasks = new Set();
|
|
24
|
+
const taskKeys = readTaskIndexKeys(env.repoRoot);
|
|
25
|
+
for (const manifestPath of manifestPaths) {
|
|
26
|
+
const runIdFromPath = extractRunIdFromManifestPath(manifestPath);
|
|
27
|
+
if (!runIdFromPath) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (seenRunIds.has(runIdFromPath)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const timestamp = parseRunIdTimestamp(runIdFromPath);
|
|
34
|
+
if (timestamp && timestamp.getTime() < cutoffMs) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
let manifest;
|
|
38
|
+
try {
|
|
39
|
+
const raw = await readFile(manifestPath, 'utf8');
|
|
40
|
+
manifest = JSON.parse(raw);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const runId = typeof manifest.run_id === 'string' && manifest.run_id ? manifest.run_id : runIdFromPath;
|
|
46
|
+
if (seenRunIds.has(runId)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
seenRunIds.add(runId);
|
|
50
|
+
const startedAtMs = Date.parse(manifest.started_at ?? '') || timestamp?.getTime() || 0;
|
|
51
|
+
if (!startedAtMs || startedAtMs < cutoffMs) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
statusCounts.total += 1;
|
|
55
|
+
if (manifest.status === 'succeeded') {
|
|
56
|
+
statusCounts.succeeded += 1;
|
|
57
|
+
}
|
|
58
|
+
else if (manifest.status === 'failed') {
|
|
59
|
+
statusCounts.failed += 1;
|
|
60
|
+
}
|
|
61
|
+
else if (manifest.status === 'cancelled') {
|
|
62
|
+
statusCounts.cancelled += 1;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
statusCounts.other += 1;
|
|
66
|
+
}
|
|
67
|
+
const pipelineId = typeof manifest.pipeline_id === 'string' && manifest.pipeline_id ? manifest.pipeline_id : 'unknown';
|
|
68
|
+
pipelines.set(pipelineId, (pipelines.get(pipelineId) ?? 0) + 1);
|
|
69
|
+
if (pipelineId === 'rlm') {
|
|
70
|
+
rlmRuns += 1;
|
|
71
|
+
}
|
|
72
|
+
if (manifest.cloud_execution) {
|
|
73
|
+
cloudRuns += 1;
|
|
74
|
+
const status = (manifest.cloud_execution.status ?? 'unknown').trim() || 'unknown';
|
|
75
|
+
cloudByStatus[status] = (cloudByStatus[status] ?? 0) + 1;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(manifest.collab_tool_calls) && manifest.collab_tool_calls.length > 0) {
|
|
78
|
+
collabRunsWithToolCalls += 1;
|
|
79
|
+
collabTotalToolCalls += manifest.collab_tool_calls.length;
|
|
80
|
+
}
|
|
81
|
+
if (taskKeys.has(manifest.task_id)) {
|
|
82
|
+
activeIndexTasks.add(manifest.task_id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const pipelineTop = [...pipelines.entries()]
|
|
86
|
+
.sort((a, b) => b[1] - a[1])
|
|
87
|
+
.slice(0, 10)
|
|
88
|
+
.map(([id, runs]) => ({ id, runs }));
|
|
89
|
+
const delegationErrors = [];
|
|
90
|
+
let activeWithSubagents = 0;
|
|
91
|
+
let totalSubagentManifests = 0;
|
|
92
|
+
const activeTasks = [...activeIndexTasks];
|
|
93
|
+
const subagentResults = await Promise.all(activeTasks.map(async (taskId) => {
|
|
94
|
+
const result = await findSubagentManifests(env.runsRoot, taskId);
|
|
95
|
+
if (result.error) {
|
|
96
|
+
delegationErrors.push(result.error);
|
|
97
|
+
}
|
|
98
|
+
return { taskId, found: result.found };
|
|
99
|
+
}));
|
|
100
|
+
for (const item of subagentResults) {
|
|
101
|
+
totalSubagentManifests += item.found.length;
|
|
102
|
+
if (item.found.length > 0) {
|
|
103
|
+
activeWithSubagents += 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
window_days: windowDays,
|
|
108
|
+
cutoff_iso: cutoffIso,
|
|
109
|
+
runs: statusCounts,
|
|
110
|
+
cloud: {
|
|
111
|
+
runs: cloudRuns,
|
|
112
|
+
by_status: cloudByStatus
|
|
113
|
+
},
|
|
114
|
+
rlm: {
|
|
115
|
+
runs: rlmRuns
|
|
116
|
+
},
|
|
117
|
+
collab: {
|
|
118
|
+
runs_with_tool_calls: collabRunsWithToolCalls,
|
|
119
|
+
total_tool_calls: collabTotalToolCalls,
|
|
120
|
+
capture_disabled: collabCaptureDisabled
|
|
121
|
+
},
|
|
122
|
+
delegation: {
|
|
123
|
+
active_top_level_tasks: activeTasks.length,
|
|
124
|
+
active_with_subagents: activeWithSubagents,
|
|
125
|
+
total_subagent_manifests: totalSubagentManifests,
|
|
126
|
+
errors: delegationErrors
|
|
127
|
+
},
|
|
128
|
+
pipelines: {
|
|
129
|
+
total: pipelines.size,
|
|
130
|
+
top: pipelineTop
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function formatDoctorUsageSummary(result) {
|
|
135
|
+
const lines = [];
|
|
136
|
+
lines.push(`Usage (last ${result.window_days}d, cutoff ${result.cutoff_iso})`);
|
|
137
|
+
lines.push(` - runs: ${result.runs.total} (ok=${result.runs.succeeded}, failed=${result.runs.failed}, cancelled=${result.runs.cancelled}, other=${result.runs.other})`);
|
|
138
|
+
lines.push(` - cloud: ${result.cloud.runs} (${formatPercent(result.cloud.runs, result.runs.total)})${formatCloudStatuses(result.cloud.by_status)}`);
|
|
139
|
+
lines.push(` - rlm: ${result.rlm.runs} (${formatPercent(result.rlm.runs, result.runs.total)})`);
|
|
140
|
+
const collabSuffix = result.collab.capture_disabled ? ' (capture disabled)' : '';
|
|
141
|
+
lines.push(` - collab: ${result.collab.runs_with_tool_calls} (${formatPercent(result.collab.runs_with_tool_calls, result.runs.total)})${collabSuffix}`);
|
|
142
|
+
if (result.delegation.active_top_level_tasks > 0) {
|
|
143
|
+
lines.push(` - delegation: ${result.delegation.active_with_subagents}/${result.delegation.active_top_level_tasks} top-level tasks have subagent manifests`);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
lines.push(' - delegation: no top-level tasks detected in tasks/index.json for this window');
|
|
147
|
+
}
|
|
148
|
+
if (result.pipelines.top.length > 0) {
|
|
149
|
+
lines.push('Top pipelines:');
|
|
150
|
+
for (const entry of result.pipelines.top) {
|
|
151
|
+
lines.push(` - ${entry.id}: ${entry.runs}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (result.delegation.errors.length > 0) {
|
|
155
|
+
lines.push('Delegation scan warnings:');
|
|
156
|
+
for (const warning of result.delegation.errors.slice(0, 3)) {
|
|
157
|
+
lines.push(` - ${warning}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return lines;
|
|
161
|
+
}
|
|
162
|
+
function extractRunIdFromManifestPath(manifestPath) {
|
|
163
|
+
if (!manifestPath) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
// .../<run-id>/manifest.json
|
|
167
|
+
const dir = manifestPath.endsWith('manifest.json') ? basename(dirname(manifestPath)) : null;
|
|
168
|
+
return dir && dir !== '..' ? dir : null;
|
|
169
|
+
}
|
|
170
|
+
function clampInt(value, min, max) {
|
|
171
|
+
const rounded = Math.floor(value);
|
|
172
|
+
if (!Number.isFinite(rounded)) {
|
|
173
|
+
return min;
|
|
174
|
+
}
|
|
175
|
+
return Math.max(min, Math.min(max, rounded));
|
|
176
|
+
}
|
|
177
|
+
function formatPercent(numerator, denominator) {
|
|
178
|
+
if (!denominator) {
|
|
179
|
+
return '0%';
|
|
180
|
+
}
|
|
181
|
+
const pct = (numerator / denominator) * 100;
|
|
182
|
+
return `${Math.round(pct * 10) / 10}%`;
|
|
183
|
+
}
|
|
184
|
+
function formatCloudStatuses(byStatus) {
|
|
185
|
+
const entries = Object.entries(byStatus);
|
|
186
|
+
if (entries.length === 0) {
|
|
187
|
+
return '';
|
|
188
|
+
}
|
|
189
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
190
|
+
const top = entries
|
|
191
|
+
.slice(0, 3)
|
|
192
|
+
.map(([status, count]) => `${status}=${count}`)
|
|
193
|
+
.join(', ');
|
|
194
|
+
return ` [${top}]`;
|
|
195
|
+
}
|
|
196
|
+
function readTaskIndexKeys(repoRoot) {
|
|
197
|
+
const indexPath = join(repoRoot, 'tasks', 'index.json');
|
|
198
|
+
try {
|
|
199
|
+
const raw = readFileSync(indexPath, 'utf8');
|
|
200
|
+
const parsed = JSON.parse(raw);
|
|
201
|
+
const items = Array.isArray(parsed?.items) ? parsed.items : [];
|
|
202
|
+
const keys = items
|
|
203
|
+
.map((item) => normalizeTaskKey(item))
|
|
204
|
+
.filter((key) => typeof key === 'string' && key.length > 0);
|
|
205
|
+
return new Set(keys);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return new Set();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -61,7 +61,7 @@ export function formatInitSummary(result, cwd) {
|
|
|
61
61
|
lines.push('No files written.');
|
|
62
62
|
}
|
|
63
63
|
lines.push('Next steps (recommended):');
|
|
64
|
-
lines.push(
|
|
64
|
+
lines.push(' - codex-orchestrator setup --yes # installs bundled skills + configures delegation/devtools wiring');
|
|
65
65
|
lines.push(' - codex-orchestrator codex setup # optional: managed/pinned Codex CLI (stock CLI works by default)');
|
|
66
66
|
return lines;
|
|
67
67
|
}
|
|
@@ -36,6 +36,12 @@ export async function installSkills(options = {}) {
|
|
|
36
36
|
skills: selectedSkills
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
export async function listBundledSkills() {
|
|
40
|
+
const pkgRoot = findPackageRoot();
|
|
41
|
+
const sourceRoot = join(pkgRoot, 'skills');
|
|
42
|
+
await assertDirectory(sourceRoot);
|
|
43
|
+
return await listSkillNames(sourceRoot);
|
|
44
|
+
}
|
|
39
45
|
export function formatSkillsInstallSummary(result, cwd = process.cwd()) {
|
|
40
46
|
const lines = [];
|
|
41
47
|
lines.push(`Skills source: ${result.sourceRoot}`);
|
package/docs/README.md
CHANGED
|
@@ -101,8 +101,10 @@ Use `npx @kbediako/codex-orchestrator resume --run <run-id>` to continue interru
|
|
|
101
101
|
## Companion Package Commands
|
|
102
102
|
- `codex-orchestrator mcp serve [--repo <path>] [--dry-run] [-- <extra args>]`: launch the MCP stdio server (delegates to `codex mcp-server`; stdout guard keeps protocol-only output, logs to stderr).
|
|
103
103
|
- `codex-orchestrator init codex [--cwd <path>] [--force]`: copy starter templates into a repo (includes `mcp-client.json` and `AGENTS.md`; no overwrite unless `--force`).
|
|
104
|
-
- `codex-orchestrator
|
|
104
|
+
- `codex-orchestrator setup [--yes]`: one-shot bootstrap for downstream users (installs bundled skills and configures delegation + DevTools wiring).
|
|
105
|
+
- `codex-orchestrator doctor [--format json] [--usage] [--apply]`: check optional tooling dependencies plus collab/cloud/delegation readiness and print enablement commands. `--usage` appends a local usage snapshot (scans `.runs/`). `--apply` plans/applies quick fixes (use with `--yes`).
|
|
105
106
|
- `codex-orchestrator devtools setup [--yes]`: print DevTools MCP setup instructions (`--yes` applies `codex mcp add ...`).
|
|
107
|
+
- `codex-orchestrator delegation setup [--yes]`: configure delegation MCP wiring (`--yes` applies `codex mcp add ...`).
|
|
106
108
|
- `codex-orchestrator skills install [--force] [--only <skills>] [--codex-home <path>]`: install bundled skills into `$CODEX_HOME/skills` (global skills remain the primary reference when installed).
|
|
107
109
|
- `codex-orchestrator self-check --format json`: emit a safe JSON health payload for smoke tests.
|
|
108
110
|
- `codex-orchestrator --version`: print the package version.
|
package/package.json
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: chrome-devtools
|
|
3
|
+
description: Control and inspect Chrome via the Chrome DevTools MCP server (navigate, interact, screenshots, console, network, perf).
|
|
4
|
+
allowed-tools: mcp__chrome-devtools__*
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Chrome DevTools (MCP)
|
|
8
|
+
|
|
9
|
+
Use this skill when you need browser-grounded evidence (UI screenshots, console errors, network failures, perf traces) or when a task requires real page interaction.
|
|
10
|
+
|
|
11
|
+
## Preflight
|
|
12
|
+
|
|
13
|
+
- Ensure the MCP server is configured: `codex-orchestrator devtools setup --yes`.
|
|
14
|
+
- If tools are missing in the current run, enable the server and restart the run:
|
|
15
|
+
- `codex -c 'mcp_servers.chrome-devtools.enabled=true' ...`
|
|
16
|
+
|
|
17
|
+
## Default Workflow
|
|
18
|
+
|
|
19
|
+
1. Open a new page and navigate to the target URL.
|
|
20
|
+
2. Wait for the page to be stable (avoid racing async renders).
|
|
21
|
+
3. Interact with the UI (click/fill/press) to reproduce the behavior.
|
|
22
|
+
4. Collect evidence:
|
|
23
|
+
- Screenshot(s) for visual state
|
|
24
|
+
- Console messages for runtime errors
|
|
25
|
+
- Network requests for failed/slow calls
|
|
26
|
+
5. Close pages when finished.
|
|
27
|
+
|
|
28
|
+
## Evidence Discipline
|
|
29
|
+
|
|
30
|
+
- Always capture at least one screenshot when validating UI behavior.
|
|
31
|
+
- When debugging, always include:
|
|
32
|
+
- `list_console_messages`
|
|
33
|
+
- `list_network_requests` (and fetch details for failures)
|
|
34
|
+
|
|
@@ -63,7 +63,7 @@ Fix by re-registering the server with a TOML-quoted override:
|
|
|
63
63
|
codex mcp remove delegation
|
|
64
64
|
codex mcp add delegation \
|
|
65
65
|
--env 'CODEX_MCP_CONFIG_OVERRIDES=delegate.mode="full"' \
|
|
66
|
-
-- codex-orchestrator delegate-server
|
|
66
|
+
-- codex-orchestrator delegate-server
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
## Server mode vs child mode (don’t mix them up)
|
|
@@ -98,7 +98,7 @@ If you want deeper recursion or longer wall-clock time for delegated runs, set R
|
|
|
98
98
|
```bash
|
|
99
99
|
codex mcp add delegation \
|
|
100
100
|
--env 'CODEX_MCP_CONFIG_OVERRIDES=rlm.max_subcall_depth=8;rlm.wall_clock_timeout_ms=14400000' \
|
|
101
|
-
-- codex-orchestrator delegate-server
|
|
101
|
+
-- codex-orchestrator delegate-server
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
For the `rlm` pipeline specifically, use:
|
|
@@ -35,16 +35,16 @@ codex exec \
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Optional (only if you need it):
|
|
38
|
-
- Add `--repo /path/to/repo`
|
|
38
|
+
- Add `--repo /path/to/repo` only when you want to pin the server to a repo even if Codex is launched outside that repo (default uses cwd).
|
|
39
39
|
- Add `-c 'features.skills=false'` for a minimal, deterministic background run.
|
|
40
40
|
- Add `-c 'delegate.mode=question_only'` when the child only needs `delegate.question.*` (and optional `delegate.status`).
|
|
41
41
|
- Add `-c 'delegate.mode=full'` when the child needs `delegate.spawn/pause/cancel` (nested delegation / run control).
|
|
42
42
|
- If the task needs external docs or APIs, enable only the relevant MCP server for that environment.
|
|
43
43
|
- If `delegate.spawn` is missing, re-register the MCP server with full mode (server config controls tool surface):
|
|
44
44
|
- `codex mcp remove delegation`
|
|
45
|
-
- `codex mcp add delegation --env 'CODEX_MCP_CONFIG_OVERRIDES=delegate.mode="full"' -- codex-orchestrator delegate-server
|
|
45
|
+
- `codex mcp add delegation --env 'CODEX_MCP_CONFIG_OVERRIDES=delegate.mode="full"' -- codex-orchestrator delegate-server`
|
|
46
46
|
- To raise RLM budgets for delegated runs, re-register with an override (TOML-quoted):
|
|
47
|
-
- `codex mcp add delegation --env 'CODEX_MCP_CONFIG_OVERRIDES=rlm.max_subcall_depth=8;rlm.wall_clock_timeout_ms=14400000' -- codex-orchestrator delegate-server
|
|
47
|
+
- `codex mcp add delegation --env 'CODEX_MCP_CONFIG_OVERRIDES=rlm.max_subcall_depth=8;rlm.wall_clock_timeout_ms=14400000' -- codex-orchestrator delegate-server`
|
|
48
48
|
|
|
49
49
|
For deeper background patterns and troubleshooting, see `DELEGATION_GUIDE.md`.
|
|
50
50
|
For runner + delegation coordination (short `--task` flow), see `docs/delegation-runner-workflow.md`.
|
|
@@ -64,8 +64,12 @@ For runner + delegation coordination (short `--task` flow), see `docs/delegation
|
|
|
64
64
|
### 0) One-time setup (register the MCP server)
|
|
65
65
|
|
|
66
66
|
- Register the delegation server once:
|
|
67
|
+
- Preferred: `codex-orchestrator setup --yes`
|
|
68
|
+
- One-shot bootstrap (installs bundled skills + configures delegation/DevTools wiring).
|
|
69
|
+
- `codex-orchestrator delegation setup --yes`
|
|
70
|
+
- Delegation-only setup (wraps `codex mcp add delegation ...` and keeps wiring discoverable via `codex-orchestrator doctor`).
|
|
67
71
|
- `codex mcp add delegation -- codex-orchestrator delegate-server`
|
|
68
|
-
- Optional
|
|
72
|
+
- Optional: append `--repo /path/to/repo` to pin the server to one repo (not recommended if you work across repos).
|
|
69
73
|
- `delegate-server` is the canonical name; `delegation-server` is supported as an alias.
|
|
70
74
|
- Per-run `-c 'mcp_servers.delegation.enabled=true'` only works **after** registration.
|
|
71
75
|
- If `delegate.*` tools are missing mid-task, start a new run with:
|