@kbediako/codex-orchestrator 0.2.0 → 0.2.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.
- package/README.md +43 -83
- package/dist/bin/codex-orchestrator.js +2 -0
- package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +50 -0
- package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +117 -5
- package/dist/orchestrator/src/cli/coStatusAttachCliShell.js +2 -2
- package/dist/orchestrator/src/cli/coStatusCliShell.js +28 -6
- package/dist/orchestrator/src/cli/codexCliShell.js +48 -1
- package/dist/orchestrator/src/cli/codexDefaultsSetup.js +217 -26
- package/dist/orchestrator/src/cli/control/controlHostSupervision.js +28 -6
- package/dist/orchestrator/src/cli/control/controlRuntime.js +17 -6
- package/dist/orchestrator/src/cli/control/controlStatusDashboard.js +6 -1
- package/dist/orchestrator/src/cli/control/selectedRunProjection.js +49 -2
- package/dist/orchestrator/src/cli/doctor.js +142 -48
- package/dist/orchestrator/src/cli/init.js +94 -1
- package/dist/orchestrator/src/cli/providerLinearChildLaneRunner.js +64 -1
- package/dist/orchestrator/src/cli/providerLinearWorkerRunner.js +1165 -69
- package/dist/orchestrator/src/cli/rlm/alignment.js +3 -3
- package/dist/orchestrator/src/cli/services/commandRunner.js +31 -0
- package/dist/orchestrator/src/cli/utils/cloudPreflight.js +202 -6
- package/dist/orchestrator/src/cli/utils/codexFeatures.js +60 -0
- package/dist/orchestrator/src/manager.js +74 -4
- package/dist/scripts/lib/docs-catalog.js +35 -1
- package/docs/README.md +333 -0
- package/docs/book/README.md +19 -0
- package/docs/book/codex-cli-0124-adoption.md +68 -0
- package/docs/book/local-hook-impact.md +73 -0
- package/docs/book/operations.md +60 -0
- package/docs/book/public-posture.md +34 -0
- package/docs/book/setup.md +91 -0
- package/docs/book/skills.md +11 -0
- package/docs/guides/codex-version-policy.md +104 -0
- package/docs/public/downstream-setup.md +25 -18
- package/package.json +4 -1
- package/plugins/codex-orchestrator/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-orchestrator/launcher.mjs +6 -4
- package/schemas/manifest.json +17 -0
- package/skills/README.md +26 -0
- package/skills/collab-subagents-first/SKILL.md +1 -1
- package/skills/delegation-usage/DELEGATION_GUIDE.md +12 -7
- package/skills/delegation-usage/SKILL.md +13 -8
- package/templates/codex/AGENTS.md +12 -10
|
@@ -6,6 +6,7 @@ import { release as osRelease } from 'node:os';
|
|
|
6
6
|
import { dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { buildDevtoolsSetupPlan, DEVTOOLS_SKILL_NAME, resolveDevtoolsReadiness } from './utils/devtools.js';
|
|
8
8
|
import { isManagedCodexCliEnabled, resolveCodexCliBin, resolveCodexCliReadiness } from './utils/codexCli.js';
|
|
9
|
+
import { codexFeatureProbeDisablesMultiAgentV2, codexFeatureProbeRejectsAgentMaxThreads, readCodexFeatureProbe } from './utils/codexFeatures.js';
|
|
9
10
|
import { resolveCodexHome } from './utils/codexPaths.js';
|
|
10
11
|
import { resolveOptionalDependency } from './utils/optionalDeps.js';
|
|
11
12
|
import { buildCloudPreflightAuthProvenance, buildCloudPreflightRequest, runCloudPreflight } from './utils/cloudPreflight.js';
|
|
@@ -13,7 +14,7 @@ import { classifyDelegationTransport, formatDelegateServerProcessSummary, inspec
|
|
|
13
14
|
import { sanitizeProviderOverrideEnv } from './utils/providerOverrideEnv.js';
|
|
14
15
|
import { hasLinearApiCredentials, hasLinearSourceBinding, resolveLinearSourceSetup } from './control/linearDispatchSource.js';
|
|
15
16
|
import { normalizeDispatchSourceProvider } from './control/trackerDispatchPilot.js';
|
|
16
|
-
import { BASELINE_AGENTS, BASELINE_MODEL, BASELINE_REVIEW_MODEL, BASELINE_REASONING_MINIMUM } from './codexDefaultsSetup.js';
|
|
17
|
+
import { BASELINE_AGENTS, BASELINE_MODEL, BASELINE_REVIEW_MODEL, BASELINE_REASONING_MINIMUM, formatModelDefaultExpectation, isLocalModelOptIn } from './codexDefaultsSetup.js';
|
|
17
18
|
import { CommandPlanner } from './adapters/CommandPlanner.js';
|
|
18
19
|
import { PipelineResolver } from './services/pipelineResolver.js';
|
|
19
20
|
import { isRepoConfigRequired } from './config/repoConfigPolicy.js';
|
|
@@ -29,6 +30,7 @@ const OPTIONAL_DEPENDENCIES = [
|
|
|
29
30
|
{ name: 'cheerio', install: 'npm install --save-dev cheerio' }
|
|
30
31
|
];
|
|
31
32
|
const PROVIDER_ROOT_RELATIVE_PATH = '.codex/providers';
|
|
33
|
+
const CODEX_DEBUG_MODELS_JSON_ENV = 'CODEX_ORCHESTRATOR_DEBUG_MODELS_JSON';
|
|
32
34
|
export function runDoctor(cwd = process.cwd()) {
|
|
33
35
|
const dependencies = OPTIONAL_DEPENDENCIES.map((entry) => {
|
|
34
36
|
const resolved = resolveOptionalDependency(entry.name, cwd);
|
|
@@ -85,11 +87,12 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
85
87
|
if (readiness.config.status !== 'ok') {
|
|
86
88
|
missing.push(`${DEVTOOLS_SKILL_NAME}-config`);
|
|
87
89
|
}
|
|
88
|
-
const codexDefaults = inspectCodexDefaultsAdvisory(process.env);
|
|
89
90
|
const codexBin = resolveCodexCliBin(process.env);
|
|
90
91
|
const managedOptIn = isManagedCodexCliEnabled(process.env);
|
|
91
92
|
const managedCodex = resolveCodexCliReadiness(process.env);
|
|
92
|
-
const
|
|
93
|
+
const featureProbe = readCodexFeatureProbe(codexBin, process.env);
|
|
94
|
+
const features = featureProbe.flags;
|
|
95
|
+
const codexDefaults = inspectCodexDefaultsAdvisory(process.env, codexBin, featureProbe);
|
|
93
96
|
const collabFeatureKey = features === null
|
|
94
97
|
? null
|
|
95
98
|
: Object.prototype.hasOwnProperty.call(features, 'multi_agent')
|
|
@@ -473,7 +476,15 @@ export function formatDoctorSummary(result) {
|
|
|
473
476
|
lines.push(` - model: ${result.codex_defaults.checks.model.status} (actual: ${result.codex_defaults.checks.model.actual ?? '<unset>'}, expected: ${result.codex_defaults.checks.model.expected})`);
|
|
474
477
|
lines.push(` - review_model: ${result.codex_defaults.checks.review_model.status} (actual: ${result.codex_defaults.checks.review_model.actual ?? '<unset>'}, expected: ${result.codex_defaults.checks.review_model.expected})`);
|
|
475
478
|
lines.push(` - model_reasoning_effort: ${result.codex_defaults.checks.model_reasoning_effort.status} (actual: ${result.codex_defaults.checks.model_reasoning_effort.actual ?? '<unset>'}, expected >= ${result.codex_defaults.checks.model_reasoning_effort.expected_minimum})`);
|
|
476
|
-
|
|
479
|
+
if (result.codex_defaults.checks.max_threads.status === 'skipped') {
|
|
480
|
+
lines.push(` - agents.max_threads: skipped (actual: ${result.codex_defaults.checks.max_threads.actual ?? '<unset>'}; ${result.codex_defaults.checks.max_threads.detail ?? 'omitted by policy'})`);
|
|
481
|
+
}
|
|
482
|
+
else if (result.codex_defaults.checks.max_threads.detail) {
|
|
483
|
+
lines.push(` - agents.max_threads: ${result.codex_defaults.checks.max_threads.status} (actual: ${result.codex_defaults.checks.max_threads.actual ?? '<unset>'}; ${result.codex_defaults.checks.max_threads.detail})`);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
lines.push(` - agents.max_threads: ${result.codex_defaults.checks.max_threads.status} (actual: ${result.codex_defaults.checks.max_threads.actual ?? '<unset>'}, expected >= ${result.codex_defaults.checks.max_threads.expected_minimum})`);
|
|
487
|
+
}
|
|
477
488
|
lines.push(` - agents.max_depth: ${result.codex_defaults.checks.max_depth.status} (actual: ${result.codex_defaults.checks.max_depth.actual ?? '<unset>'}, expected >= ${result.codex_defaults.checks.max_depth.expected_minimum} when set; <unset> accepted)`);
|
|
478
489
|
if (result.codex_defaults.legacy_max_spawn_depth?.present) {
|
|
479
490
|
lines.push(` - legacy agents.max_spawn_depth: ${result.codex_defaults.legacy_max_spawn_depth.status} (actual: ${result.codex_defaults.legacy_max_spawn_depth.actual ?? '<unset>'}; ${result.codex_defaults.legacy_max_spawn_depth.detail})`);
|
|
@@ -661,11 +672,15 @@ function inspectProviderReadiness(repoRoot, env = process.env) {
|
|
|
661
672
|
]
|
|
662
673
|
};
|
|
663
674
|
}
|
|
664
|
-
function inspectCodexDefaultsAdvisory(env = process.env) {
|
|
675
|
+
function inspectCodexDefaultsAdvisory(env = process.env, codexBin = resolveCodexCliBin(env), featureProbe = readCodexFeatureProbe(codexBin, env)) {
|
|
665
676
|
const configPath = join(resolveCodexHome(env), 'config.toml');
|
|
666
677
|
const checks = {
|
|
667
|
-
model: { status: 'advisory', expected: BASELINE_MODEL, actual: null },
|
|
668
|
-
review_model: {
|
|
678
|
+
model: { status: 'advisory', expected: formatModelDefaultExpectation(BASELINE_MODEL), actual: null },
|
|
679
|
+
review_model: {
|
|
680
|
+
status: 'advisory',
|
|
681
|
+
expected: formatModelDefaultExpectation(BASELINE_REVIEW_MODEL),
|
|
682
|
+
actual: null
|
|
683
|
+
},
|
|
669
684
|
model_reasoning_effort: { status: 'advisory', expected_minimum: BASELINE_REASONING_MINIMUM, actual: null },
|
|
670
685
|
max_threads: { status: 'advisory', expected_minimum: BASELINE_AGENTS.max_threads, actual: null },
|
|
671
686
|
max_depth: { status: 'advisory', expected_minimum: BASELINE_AGENTS.max_depth, actual: null }
|
|
@@ -673,10 +688,21 @@ function inspectCodexDefaultsAdvisory(env = process.env) {
|
|
|
673
688
|
let legacyMaxSpawnDepth = null;
|
|
674
689
|
const guidance = [
|
|
675
690
|
'Run `codex-orchestrator codex defaults --yes` to apply additive baseline defaults.',
|
|
676
|
-
'Additive policy: unrelated config keys are preserved; existing role files stay untouched unless `--force` is set.',
|
|
691
|
+
'Additive policy: unrelated config keys are preserved; existing role files stay untouched unless `--force` is set or they exactly match a prior CO-managed model baseline.',
|
|
692
|
+
'When `features.multi_agent_v2=true`, omit `agents.max_threads`; Codex CLI 0.125+ rejects that key.',
|
|
677
693
|
'Current CO baseline no longer seeds or expects `agents.max_spawn_depth`; keep it only as a legacy local override when an older parser/runtime still honors it.',
|
|
678
694
|
'Leaving `agents.max_depth` unset remains accepted when local parser/runtime constraints require it.'
|
|
679
695
|
];
|
|
696
|
+
const featureProbeDisablesMultiAgentV2 = featureProbe ? codexFeatureProbeDisablesMultiAgentV2(featureProbe) : false;
|
|
697
|
+
const featureProbeIndicatesMultiAgentV2 = !featureProbeDisablesMultiAgentV2
|
|
698
|
+
&& (featureProbe?.flags?.multi_agent_v2 === true
|
|
699
|
+
|| (featureProbe ? codexFeatureProbeRejectsAgentMaxThreads(featureProbe) : false));
|
|
700
|
+
if (featureProbeIndicatesMultiAgentV2) {
|
|
701
|
+
checks.max_threads.status = 'skipped';
|
|
702
|
+
checks.max_threads.actual = null;
|
|
703
|
+
checks.max_threads.detail =
|
|
704
|
+
'features.multi_agent_v2=true; omit agents.max_threads because Codex CLI 0.125+ rejects it';
|
|
705
|
+
}
|
|
680
706
|
if (!existsSync(configPath)) {
|
|
681
707
|
return {
|
|
682
708
|
status: 'advisory',
|
|
@@ -710,23 +736,55 @@ function inspectCodexDefaultsAdvisory(env = process.env) {
|
|
|
710
736
|
};
|
|
711
737
|
}
|
|
712
738
|
const model = normalizeOptionalString(readStringValue(parsed.model));
|
|
713
|
-
checks.model.actual = model;
|
|
714
|
-
checks.model.status = model === BASELINE_MODEL ? 'ok' : 'advisory';
|
|
715
739
|
const reviewModel = normalizeOptionalString(readStringValue(parsed.review_model));
|
|
740
|
+
const localModelCandidates = new Set([model, reviewModel].filter(isLocalModelOptIn));
|
|
741
|
+
const modelAccess = localModelCandidates.size === 0
|
|
742
|
+
? {
|
|
743
|
+
status: 'unavailable',
|
|
744
|
+
models: new Set(),
|
|
745
|
+
detail: 'model access was not checked because no local ChatGPT-auth model is configured'
|
|
746
|
+
}
|
|
747
|
+
: inspectCodexModelAccess(codexBin, env);
|
|
748
|
+
const verifiedLocalModels = new Set([...localModelCandidates].filter((candidate) => modelAccess.status === 'ok' && modelAccess.models.has(candidate)));
|
|
749
|
+
const unverifiedLocalModels = [...localModelCandidates].filter((candidate) => !verifiedLocalModels.has(candidate));
|
|
750
|
+
for (const candidate of unverifiedLocalModels) {
|
|
751
|
+
guidance.push(`Configured local ChatGPT-auth model ${candidate} is not verified by \`codex debug models\`; rerun local access smoke or use the portable ${BASELINE_MODEL} fallback for this surface.`);
|
|
752
|
+
}
|
|
753
|
+
checks.model.actual = model;
|
|
754
|
+
checks.model.status =
|
|
755
|
+
model === BASELINE_MODEL || (isLocalModelOptIn(model) && verifiedLocalModels.has(model))
|
|
756
|
+
? 'ok'
|
|
757
|
+
: 'advisory';
|
|
716
758
|
checks.review_model.actual = reviewModel;
|
|
717
|
-
checks.review_model.status =
|
|
759
|
+
checks.review_model.status =
|
|
760
|
+
reviewModel === BASELINE_REVIEW_MODEL || (isLocalModelOptIn(reviewModel) && verifiedLocalModels.has(reviewModel))
|
|
761
|
+
? 'ok'
|
|
762
|
+
: 'advisory';
|
|
718
763
|
const reasoning = normalizeOptionalString(readStringValue(parsed.model_reasoning_effort));
|
|
719
764
|
checks.model_reasoning_effort.actual = reasoning;
|
|
720
765
|
checks.model_reasoning_effort.status = isReasoningAtLeastMinimum(reasoning, BASELINE_REASONING_MINIMUM)
|
|
721
766
|
? 'ok'
|
|
722
767
|
: 'advisory';
|
|
723
768
|
const agents = isRecord(parsed.agents) ? parsed.agents : {};
|
|
769
|
+
const multiAgentV2Enabled = featureProbeIndicatesMultiAgentV2
|
|
770
|
+
|| (!featureProbeDisablesMultiAgentV2
|
|
771
|
+
&& readBooleanValue(readRecordValue(parsed, 'features')?.multi_agent_v2) === true);
|
|
724
772
|
const maxThreads = readNumberValue(agents.max_threads);
|
|
725
773
|
const maxDepth = readNumberValue(agents.max_depth);
|
|
726
774
|
const maxSpawnDepth = readNumberValue(agents.max_spawn_depth);
|
|
775
|
+
const hasMaxThreads = Object.prototype.hasOwnProperty.call(agents, 'max_threads');
|
|
727
776
|
checks.max_threads.actual = maxThreads;
|
|
728
|
-
|
|
729
|
-
|
|
777
|
+
if (multiAgentV2Enabled) {
|
|
778
|
+
checks.max_threads.status = hasMaxThreads ? 'advisory' : 'skipped';
|
|
779
|
+
checks.max_threads.detail =
|
|
780
|
+
hasMaxThreads
|
|
781
|
+
? 'features.multi_agent_v2=true; remove agents.max_threads because Codex CLI 0.125+ rejects it'
|
|
782
|
+
: 'features.multi_agent_v2=true; omit agents.max_threads because Codex CLI 0.125+ rejects it';
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
checks.max_threads.status =
|
|
786
|
+
typeof maxThreads === 'number' && maxThreads >= BASELINE_AGENTS.max_threads ? 'ok' : 'advisory';
|
|
787
|
+
}
|
|
730
788
|
checks.max_depth.actual = maxDepth;
|
|
731
789
|
checks.max_depth.status =
|
|
732
790
|
maxDepth === null || (typeof maxDepth === 'number' && maxDepth >= BASELINE_AGENTS.max_depth) ? 'ok' : 'advisory';
|
|
@@ -741,7 +799,8 @@ function inspectCodexDefaultsAdvisory(env = process.env) {
|
|
|
741
799
|
: `older parser/runtime may still treat this as a hard cap below the CO baseline depth; raise it to >= ${BASELINE_AGENTS.max_depth} or remove it`
|
|
742
800
|
};
|
|
743
801
|
}
|
|
744
|
-
const allChecksOk = Object.values(checks).every((check) => check.status === 'ok')
|
|
802
|
+
const allChecksOk = Object.values(checks).every((check) => check.status === 'ok' || check.status === 'skipped')
|
|
803
|
+
&& unverifiedLocalModels.length === 0
|
|
745
804
|
&& legacyMaxSpawnDepth?.status !== 'advisory';
|
|
746
805
|
return {
|
|
747
806
|
status: allChecksOk ? 'ok' : 'advisory',
|
|
@@ -751,6 +810,69 @@ function inspectCodexDefaultsAdvisory(env = process.env) {
|
|
|
751
810
|
guidance
|
|
752
811
|
};
|
|
753
812
|
}
|
|
813
|
+
function inspectCodexModelAccess(codexBin, env) {
|
|
814
|
+
const overrideJson = env[CODEX_DEBUG_MODELS_JSON_ENV];
|
|
815
|
+
if (typeof overrideJson === 'string' && overrideJson.trim().length > 0) {
|
|
816
|
+
return parseCodexDebugModels(overrideJson, `${CODEX_DEBUG_MODELS_JSON_ENV} override`);
|
|
817
|
+
}
|
|
818
|
+
const result = spawnSync(codexBin, ['debug', 'models'], {
|
|
819
|
+
encoding: 'utf8',
|
|
820
|
+
env,
|
|
821
|
+
timeout: 5000,
|
|
822
|
+
maxBuffer: 5 * 1024 * 1024
|
|
823
|
+
});
|
|
824
|
+
if (result.error) {
|
|
825
|
+
return {
|
|
826
|
+
status: 'unavailable',
|
|
827
|
+
models: new Set(),
|
|
828
|
+
detail: result.error.message
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
if (result.status !== 0) {
|
|
832
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : '';
|
|
833
|
+
return {
|
|
834
|
+
status: 'unavailable',
|
|
835
|
+
models: new Set(),
|
|
836
|
+
detail: stderr || `codex debug models exited ${result.status ?? 'without a status'}`
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return parseCodexDebugModels(result.stdout, '`codex debug models`');
|
|
840
|
+
}
|
|
841
|
+
function parseCodexDebugModels(source, detailPrefix) {
|
|
842
|
+
if (typeof source !== 'string' || source.trim().length === 0) {
|
|
843
|
+
return {
|
|
844
|
+
status: 'unavailable',
|
|
845
|
+
models: new Set(),
|
|
846
|
+
detail: `${detailPrefix} produced no model catalog output`
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
const parsed = JSON.parse(source);
|
|
851
|
+
const models = new Set();
|
|
852
|
+
const entries = isRecord(parsed) && Array.isArray(parsed.models) ? parsed.models : [];
|
|
853
|
+
for (const entry of entries) {
|
|
854
|
+
if (!isRecord(entry)) {
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const slug = readStringValue(entry.slug);
|
|
858
|
+
if (slug) {
|
|
859
|
+
models.add(slug);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
status: models.size > 0 ? 'ok' : 'unavailable',
|
|
864
|
+
models,
|
|
865
|
+
detail: `${detailPrefix} reported ${models.size} model(s)`
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
catch (error) {
|
|
869
|
+
return {
|
|
870
|
+
status: 'unavailable',
|
|
871
|
+
models: new Set(),
|
|
872
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
754
876
|
function getTomlParser() {
|
|
755
877
|
if (tomlParser) {
|
|
756
878
|
return tomlParser;
|
|
@@ -1077,6 +1199,12 @@ function buildCloudPreflightGuidance(issues) {
|
|
|
1077
1199
|
case 'missing_environment':
|
|
1078
1200
|
guidance.push('Set CODEX_CLOUD_ENV_ID or provide target metadata.cloudEnvId.');
|
|
1079
1201
|
break;
|
|
1202
|
+
case 'environment_not_found':
|
|
1203
|
+
guidance.push('Set CODEX_CLOUD_ENV_ID to a Codex Cloud environment visible to the active account; run `codex cloud` to list available environments.');
|
|
1204
|
+
break;
|
|
1205
|
+
case 'environment_unavailable':
|
|
1206
|
+
guidance.push('Verify the active Codex account/profile can read CODEX_CLOUD_ENV_ID before running required cloud canaries.');
|
|
1207
|
+
break;
|
|
1080
1208
|
case 'branch_missing':
|
|
1081
1209
|
guidance.push('Push the branch to origin or set CODEX_CLOUD_BRANCH to an existing remote branch.');
|
|
1082
1210
|
break;
|
|
@@ -1095,40 +1223,6 @@ function buildCloudPreflightGuidance(issues) {
|
|
|
1095
1223
|
}
|
|
1096
1224
|
return [...new Set(guidance)];
|
|
1097
1225
|
}
|
|
1098
|
-
function readCodexFeatureFlags(codexBin) {
|
|
1099
|
-
const result = spawnSync(codexBin, ['features', 'list'], {
|
|
1100
|
-
encoding: 'utf8',
|
|
1101
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1102
|
-
timeout: 5000
|
|
1103
|
-
});
|
|
1104
|
-
if (result.error || result.status !== 0) {
|
|
1105
|
-
return null;
|
|
1106
|
-
}
|
|
1107
|
-
const stdout = String(result.stdout ?? '');
|
|
1108
|
-
const flags = {};
|
|
1109
|
-
for (const line of stdout.split(/\r?\n/u)) {
|
|
1110
|
-
const trimmed = line.trim();
|
|
1111
|
-
if (!trimmed) {
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
const tokens = trimmed.split(/\s+/u);
|
|
1115
|
-
if (tokens.length < 2) {
|
|
1116
|
-
continue;
|
|
1117
|
-
}
|
|
1118
|
-
const name = tokens[0] ?? '';
|
|
1119
|
-
const enabledToken = tokens[tokens.length - 1] ?? '';
|
|
1120
|
-
if (!name) {
|
|
1121
|
-
continue;
|
|
1122
|
-
}
|
|
1123
|
-
if (enabledToken === 'true') {
|
|
1124
|
-
flags[name] = true;
|
|
1125
|
-
}
|
|
1126
|
-
else if (enabledToken === 'false') {
|
|
1127
|
-
flags[name] = false;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
return flags;
|
|
1131
|
-
}
|
|
1132
1226
|
function canRunCommand(command, args) {
|
|
1133
1227
|
const result = spawnSync(command, args, {
|
|
1134
1228
|
encoding: 'utf8',
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';
|
|
1
|
+
import { copyFile, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { dirname, join, relative } from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { resolveCodexHome } from './utils/codexPaths.js';
|
|
7
|
+
import { resolveCodexCliBin } from './utils/codexCli.js';
|
|
8
|
+
import { codexFeatureProbeDisablesMultiAgentV2, codexFeatureProbeRejectsAgentMaxThreads, readCodexFeatureProbe } from './utils/codexFeatures.js';
|
|
4
9
|
import { findPackageRoot } from './utils/packageInfo.js';
|
|
5
10
|
const CODEX_TEMPLATE = 'codex';
|
|
6
11
|
const CODEX_PIPELINE_CONFIG = 'codex.orchestrator.json';
|
|
12
|
+
const CODEX_CONFIG_TEMPLATE = join('.codex', 'config.toml');
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
let tomlLibrary;
|
|
7
15
|
export async function initCodexTemplates(options) {
|
|
8
16
|
const root = findPackageRoot();
|
|
9
17
|
const templateRoot = join(root, 'templates', options.template);
|
|
18
|
+
const env = options.env ?? process.env;
|
|
10
19
|
const written = [];
|
|
11
20
|
const skipped = [];
|
|
12
21
|
await assertDirectory(templateRoot);
|
|
@@ -16,6 +25,10 @@ export async function initCodexTemplates(options) {
|
|
|
16
25
|
skipped
|
|
17
26
|
});
|
|
18
27
|
if (options.template === CODEX_TEMPLATE) {
|
|
28
|
+
const configPath = join(options.cwd, CODEX_CONFIG_TEMPLATE);
|
|
29
|
+
if (written.includes(configPath) && await isMultiAgentV2Enabled(env)) {
|
|
30
|
+
await omitAgentMaxThreads(configPath);
|
|
31
|
+
}
|
|
19
32
|
await copyTemplateFile(join(root, CODEX_PIPELINE_CONFIG), join(options.cwd, CODEX_PIPELINE_CONFIG), {
|
|
20
33
|
force: options.force,
|
|
21
34
|
written,
|
|
@@ -24,6 +37,86 @@ export async function initCodexTemplates(options) {
|
|
|
24
37
|
}
|
|
25
38
|
return { written, skipped, templateRoot };
|
|
26
39
|
}
|
|
40
|
+
async function isMultiAgentV2Enabled(env) {
|
|
41
|
+
const featureProbe = readCodexFeatureProbe(resolveCodexCliBin(env), env);
|
|
42
|
+
if (featureProbe.flags?.multi_agent_v2 === true) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (codexFeatureProbeDisablesMultiAgentV2(featureProbe)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (codexFeatureProbeRejectsAgentMaxThreads(featureProbe)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
const configPath = join(resolveCodexHome(env), 'config.toml');
|
|
52
|
+
if (!existsSync(configPath)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
let raw;
|
|
56
|
+
try {
|
|
57
|
+
raw = await readFile(configPath, 'utf8');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = getTomlLibrary().parse(raw);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (!isRecord(parsed) || !isRecord(parsed.features)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return parsed.features.multi_agent_v2 === true;
|
|
73
|
+
}
|
|
74
|
+
async function omitAgentMaxThreads(configPath) {
|
|
75
|
+
const raw = await readFile(configPath, 'utf8');
|
|
76
|
+
const { removed, text } = removeAgentMaxThreadsFromToml(raw);
|
|
77
|
+
if (!removed) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await writeFile(configPath, text, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
function removeAgentMaxThreadsFromToml(raw) {
|
|
83
|
+
const lines = raw.split(/(?<=\n)/u);
|
|
84
|
+
let inAgentsTable = false;
|
|
85
|
+
let removed = false;
|
|
86
|
+
const kept = [];
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
const withoutEol = line.replace(/\r?\n$/u, '');
|
|
89
|
+
const trimmed = withoutEol.trim();
|
|
90
|
+
if (/^\[[^\]]+\]\s*(?:#.*)?$/u.test(trimmed)) {
|
|
91
|
+
inAgentsTable = /^\[agents\]\s*(?:#.*)?$/u.test(trimmed);
|
|
92
|
+
}
|
|
93
|
+
if (inAgentsTable && /^[ \t]*max_threads\s*=/u.test(withoutEol)) {
|
|
94
|
+
removed = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
kept.push(line);
|
|
98
|
+
}
|
|
99
|
+
return { removed, text: kept.join('') };
|
|
100
|
+
}
|
|
101
|
+
function getTomlLibrary() {
|
|
102
|
+
if (tomlLibrary) {
|
|
103
|
+
return tomlLibrary;
|
|
104
|
+
}
|
|
105
|
+
if (tomlLibrary === null) {
|
|
106
|
+
throw new Error('Failed to load @iarna/toml.');
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
tomlLibrary = require('@iarna/toml');
|
|
110
|
+
return tomlLibrary;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
tomlLibrary = null;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function isRecord(value) {
|
|
118
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
119
|
+
}
|
|
27
120
|
async function assertDirectory(path) {
|
|
28
121
|
const info = await stat(path).catch(() => null);
|
|
29
122
|
if (!info || !info.isDirectory()) {
|
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { logger } from '../logger.js';
|
|
10
10
|
import { acquireLockWithRetry } from '../persistence/lockFile.js';
|
|
11
11
|
import { writeAtomicFile } from '../utils/atomicWrite.js';
|
|
12
|
-
import { buildEmptyProviderLinearWorkerTokenUsage, defaultExecRunner, parseProviderLinearWorkerJsonl } from './providerLinearWorkerRunner.js';
|
|
12
|
+
import { buildEmptyProviderLinearWorkerTokenUsage, defaultExecRunner, parseProviderLinearWorkerJsonl, PROVIDER_LINEAR_CHILD_LANE_DIAGNOSTICS_FILENAME } from './providerLinearWorkerRunner.js';
|
|
13
13
|
import { createRuntimeCodexCommandContext, formatRuntimeSelectionSummary, parseRuntimeMode, resolveRuntimeCodexCommand } from './runtime/index.js';
|
|
14
14
|
import { buildRunMemoryPromptLines, selectRunMemoryForRole } from './run/runMemoryController.js';
|
|
15
15
|
import { resolveProviderLinearChildLaneScopeContract } from './providerLinearChildLanePhaseContract.js';
|
|
@@ -22,6 +22,7 @@ const PROVIDER_LINEAR_CHILD_LANE_SCOPE_DRIFT_POLL_INTERVAL_MS = 250;
|
|
|
22
22
|
const PROVIDER_LINEAR_CHILD_LANE_SESSION_LOG_DISCOVERY_WINDOW_MS = 10 * 60 * 1000;
|
|
23
23
|
const PROVIDER_LINEAR_CHILD_LANE_SESSION_LOG_HEADER_BYTES = 256 * 1024;
|
|
24
24
|
const PROVIDER_LINEAR_CHILD_LANE_SESSION_LOG_MTIME_SKEW_MS = 1000;
|
|
25
|
+
const PROVIDER_LINEAR_CHILD_LANE_RUNNER_ENTRYPOINT = 'providerLinearChildLaneRunner';
|
|
25
26
|
let tomlParser;
|
|
26
27
|
const PROVIDER_LINEAR_CHILD_LANE_TRUSTED_PROJECT_CONFIG_LOCK_RETRY = {
|
|
27
28
|
maxAttempts: 50,
|
|
@@ -1501,6 +1502,32 @@ function resolveProviderLinearChildLaneUnauthorizedCommitMessage(input) {
|
|
|
1501
1502
|
async function writeChildLaneProof(runDir, proof) {
|
|
1502
1503
|
await writeFile(join(runDir, PROVIDER_LINEAR_CHILD_LANE_PROOF_FILENAME), `${JSON.stringify(proof, null, 2)}\n`, 'utf8');
|
|
1503
1504
|
}
|
|
1505
|
+
async function writeProviderLinearChildLaneDiagnostics(context, patch) {
|
|
1506
|
+
const diagnosticsPath = join(context.runDir, PROVIDER_LINEAR_CHILD_LANE_DIAGNOSTICS_FILENAME);
|
|
1507
|
+
try {
|
|
1508
|
+
let existing = {};
|
|
1509
|
+
try {
|
|
1510
|
+
const parsed = JSON.parse(await readFile(diagnosticsPath, 'utf8'));
|
|
1511
|
+
existing = isRecord(parsed) ? parsed : {};
|
|
1512
|
+
}
|
|
1513
|
+
catch {
|
|
1514
|
+
existing = {};
|
|
1515
|
+
}
|
|
1516
|
+
await writeAtomicFile(diagnosticsPath, `${JSON.stringify({
|
|
1517
|
+
...existing,
|
|
1518
|
+
issue_id: context.issueId,
|
|
1519
|
+
issue_identifier: context.issueIdentifier,
|
|
1520
|
+
task_id: context.taskId,
|
|
1521
|
+
run_id: context.runId,
|
|
1522
|
+
parent_run_id: context.parentRunId,
|
|
1523
|
+
stream: context.stream,
|
|
1524
|
+
...patch
|
|
1525
|
+
}, null, 2)}\n`, { ensureDir: true, encoding: 'utf8' });
|
|
1526
|
+
}
|
|
1527
|
+
catch (error) {
|
|
1528
|
+
logger.warn(`[provider-linear-child-lane-diagnostics] failed to persist ${basename(diagnosticsPath)} at ${diagnosticsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1504
1531
|
function buildFailedChildLaneProof(input) {
|
|
1505
1532
|
return {
|
|
1506
1533
|
issue_id: input.context.issueId,
|
|
@@ -1543,12 +1570,35 @@ export async function runProviderLinearChildLane(env = process.env, dependencyOv
|
|
|
1543
1570
|
...dependencyOverrides
|
|
1544
1571
|
};
|
|
1545
1572
|
const context = await loadProviderLinearChildLaneContext(env);
|
|
1573
|
+
const runnerStartedAt = deps.now();
|
|
1574
|
+
await writeProviderLinearChildLaneDiagnostics(context, {
|
|
1575
|
+
provider_linear_child_lane_runner_entrypoint: PROVIDER_LINEAR_CHILD_LANE_RUNNER_ENTRYPOINT,
|
|
1576
|
+
provider_linear_child_lane_runner_pid: process.pid,
|
|
1577
|
+
provider_linear_child_lane_runner_started_at: runnerStartedAt,
|
|
1578
|
+
provider_linear_child_lane_runtime_event: 'runner_started',
|
|
1579
|
+
provider_linear_child_lane_runtime_event_at: runnerStartedAt
|
|
1580
|
+
});
|
|
1546
1581
|
const { laneWorkspacePath, laneBranch } = await prepareLaneWorkspace(context);
|
|
1547
1582
|
const startingHeadSha = await readProviderLinearChildLaneHeadSha(laneWorkspacePath).catch(() => null);
|
|
1548
1583
|
const startingReflogEntryCount = await countProviderLinearChildLaneReflogEntries(laneWorkspacePath).catch(() => 0);
|
|
1549
1584
|
try {
|
|
1550
1585
|
const runtimeContext = await resolveChildLaneRuntimeContext(env, laneWorkspacePath, context.runId);
|
|
1551
1586
|
logger.info(`[provider-linear-child-lane-runtime] ${formatRuntimeSelectionSummary(runtimeContext.runtime)}`);
|
|
1587
|
+
await writeProviderLinearChildLaneDiagnostics(context, {
|
|
1588
|
+
provider_linear_child_lane_runtime_requested_mode: runtimeContext.runtime.requested_mode,
|
|
1589
|
+
provider_linear_child_lane_runtime_selected_mode: runtimeContext.runtime.selected_mode,
|
|
1590
|
+
provider_linear_child_lane_runtime_source: runtimeContext.runtime.source,
|
|
1591
|
+
provider_linear_child_lane_runtime_provider: runtimeContext.runtime.provider,
|
|
1592
|
+
provider_linear_child_lane_runtime_session_id: runtimeContext.runtime.runtime_session_id,
|
|
1593
|
+
provider_linear_child_lane_runtime_fallback_occurred: runtimeContext.runtime.fallback.occurred,
|
|
1594
|
+
provider_linear_child_lane_runtime_fallback_code: runtimeContext.runtime.fallback.code,
|
|
1595
|
+
provider_linear_child_lane_runtime_fallback_reason: runtimeContext.runtime.fallback.reason,
|
|
1596
|
+
provider_linear_child_lane_runtime_fallback_from_mode: runtimeContext.runtime.fallback.from_mode,
|
|
1597
|
+
provider_linear_child_lane_runtime_fallback_to_mode: runtimeContext.runtime.fallback.to_mode,
|
|
1598
|
+
provider_linear_child_lane_runtime_fallback_checked_at: runtimeContext.runtime.fallback.checked_at,
|
|
1599
|
+
provider_linear_child_lane_runtime_event: 'runtime_selected',
|
|
1600
|
+
provider_linear_child_lane_runtime_event_at: deps.now()
|
|
1601
|
+
});
|
|
1552
1602
|
const childEnv = { ...process.env, ...env, ...runtimeContext.env };
|
|
1553
1603
|
childEnv.CODEX_NON_INTERACTIVE = '1';
|
|
1554
1604
|
childEnv.CODEX_NO_INTERACTIVE = '1';
|
|
@@ -1594,6 +1644,14 @@ export async function runProviderLinearChildLane(env = process.env, dependencyOv
|
|
|
1594
1644
|
else {
|
|
1595
1645
|
if (startupRace.sessionLogPath) {
|
|
1596
1646
|
logger.info(`[provider-linear-child-lane-runtime] appserver startup observed via session log ${basename(startupRace.sessionLogPath)}`);
|
|
1647
|
+
const startupObservedAt = deps.now();
|
|
1648
|
+
await writeProviderLinearChildLaneDiagnostics(context, {
|
|
1649
|
+
provider_linear_child_lane_runtime_event: 'appserver_startup_observed',
|
|
1650
|
+
provider_linear_child_lane_runtime_event_at: startupObservedAt,
|
|
1651
|
+
provider_linear_child_lane_appserver_startup_observed: true,
|
|
1652
|
+
provider_linear_child_lane_appserver_startup_observed_at: startupObservedAt,
|
|
1653
|
+
provider_linear_child_lane_appserver_session_log: basename(startupRace.sessionLogPath)
|
|
1654
|
+
});
|
|
1597
1655
|
}
|
|
1598
1656
|
const execOrDriftRace = startupRace.sessionLogPath
|
|
1599
1657
|
? await Promise.race([
|
|
@@ -1659,6 +1717,11 @@ export async function runProviderLinearChildLane(env = process.env, dependencyOv
|
|
|
1659
1717
|
if (!execResult) {
|
|
1660
1718
|
throw new Error('provider-linear-child-lane completed without an exec result');
|
|
1661
1719
|
}
|
|
1720
|
+
await writeProviderLinearChildLaneDiagnostics(context, {
|
|
1721
|
+
provider_linear_child_lane_runtime_event: 'codex_exec_completed',
|
|
1722
|
+
provider_linear_child_lane_runtime_event_at: deps.now(),
|
|
1723
|
+
provider_linear_child_lane_exec_exit_code: execResult.exitCode
|
|
1724
|
+
});
|
|
1662
1725
|
const parsed = parseProviderLinearWorkerJsonl(execResult.stdout);
|
|
1663
1726
|
const session = deriveLatestTurnSessionId({
|
|
1664
1727
|
threadId: parsed.threadId,
|