@kbediako/codex-orchestrator 0.1.1 → 0.1.3
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 +11 -8
- package/dist/bin/codex-orchestrator.js +245 -121
- package/dist/orchestrator/src/cli/config/userConfig.js +86 -12
- package/dist/orchestrator/src/cli/devtoolsSetup.js +66 -0
- package/dist/orchestrator/src/cli/doctor.js +46 -21
- package/dist/orchestrator/src/cli/exec/context.js +5 -2
- package/dist/orchestrator/src/cli/exec/learning.js +5 -3
- package/dist/orchestrator/src/cli/exec/stageRunner.js +1 -1
- package/dist/orchestrator/src/cli/exec/summary.js +1 -1
- package/dist/orchestrator/src/cli/orchestrator.js +16 -7
- package/dist/orchestrator/src/cli/pipelines/index.js +13 -24
- package/dist/orchestrator/src/cli/rlm/prompt.js +31 -0
- package/dist/orchestrator/src/cli/rlm/runner.js +177 -0
- package/dist/orchestrator/src/cli/rlm/types.js +1 -0
- package/dist/orchestrator/src/cli/rlm/validator.js +159 -0
- package/dist/orchestrator/src/cli/rlmRunner.js +417 -0
- package/dist/orchestrator/src/cli/run/environment.js +4 -11
- package/dist/orchestrator/src/cli/run/manifest.js +7 -1
- package/dist/orchestrator/src/cli/services/commandRunner.js +1 -1
- package/dist/orchestrator/src/cli/services/controlPlaneService.js +3 -1
- package/dist/orchestrator/src/cli/services/execRuntime.js +1 -2
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +33 -2
- package/dist/orchestrator/src/cli/services/runPreparation.js +7 -1
- package/dist/orchestrator/src/cli/services/schedulerService.js +1 -1
- package/dist/orchestrator/src/cli/utils/devtools.js +178 -0
- package/dist/orchestrator/src/cli/utils/specGuardRunner.js +3 -1
- package/dist/orchestrator/src/cli/utils/strings.js +8 -6
- package/dist/orchestrator/src/persistence/ExperienceStore.js +6 -16
- package/dist/orchestrator/src/persistence/TaskStateStore.js +1 -1
- package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +1 -1
- package/dist/packages/orchestrator/src/exec/stdio.js +112 -0
- package/dist/packages/orchestrator/src/exec/unified-exec.js +1 -1
- package/dist/packages/orchestrator/src/index.js +1 -0
- package/dist/packages/shared/design-artifacts/writer.js +4 -14
- package/dist/packages/shared/streams/stdio.js +2 -112
- package/dist/packages/shared/utils/strings.js +17 -0
- package/dist/scripts/design/pipeline/advanced-assets.js +1 -1
- package/dist/scripts/design/pipeline/context.js +5 -5
- package/dist/scripts/design/pipeline/extract.js +9 -6
- package/dist/scripts/design/pipeline/{optionalDeps.js → optional-deps.js} +49 -38
- package/dist/scripts/design/pipeline/permit.js +59 -0
- package/dist/scripts/design/pipeline/toolkit/common.js +18 -32
- package/dist/scripts/design/pipeline/toolkit/reference.js +1 -1
- package/dist/scripts/design/pipeline/toolkit/snapshot.js +1 -1
- package/dist/scripts/design/pipeline/visual-regression.js +2 -11
- package/dist/scripts/lib/cli-args.js +53 -0
- package/dist/scripts/lib/docs-helpers.js +111 -0
- package/dist/scripts/lib/npm-pack.js +20 -0
- package/dist/scripts/lib/run-manifests.js +160 -0
- package/package.json +17 -6
- package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +0 -32
- package/dist/orchestrator/src/cli/pipelines/designReference.js +0 -72
- package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +0 -71
- package/dist/orchestrator/src/cli/utils/jsonlWriter.js +0 -10
- package/dist/orchestrator/src/control-plane/index.js +0 -3
- package/dist/orchestrator/src/persistence/identifierGuards.js +0 -1
- package/dist/orchestrator/src/persistence/writeAtomicFile.js +0 -4
- package/dist/orchestrator/src/scheduler/index.js +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { buildDevtoolsSetupPlan, resolveDevtoolsReadiness } from './utils/devtools.js';
|
|
4
|
+
export async function runDevtoolsSetup(options = {}) {
|
|
5
|
+
const env = options.env ?? process.env;
|
|
6
|
+
const plan = buildDevtoolsSetupPlan(env);
|
|
7
|
+
const readiness = resolveDevtoolsReadiness(env);
|
|
8
|
+
if (!options.apply) {
|
|
9
|
+
return { status: 'planned', plan, readiness };
|
|
10
|
+
}
|
|
11
|
+
if (readiness.config.status === 'ok') {
|
|
12
|
+
return {
|
|
13
|
+
status: 'skipped',
|
|
14
|
+
reason: 'DevTools MCP is already configured.',
|
|
15
|
+
plan,
|
|
16
|
+
readiness
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (readiness.config.status === 'invalid') {
|
|
20
|
+
throw new Error(`Cannot apply DevTools setup because config.toml is invalid: ${readiness.config.path}`);
|
|
21
|
+
}
|
|
22
|
+
await applyDevtoolsSetup(plan, env);
|
|
23
|
+
return { status: 'applied', plan, readiness };
|
|
24
|
+
}
|
|
25
|
+
export function formatDevtoolsSetupSummary(result) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(`DevTools setup: ${result.status}`);
|
|
28
|
+
if (result.reason) {
|
|
29
|
+
lines.push(`Note: ${result.reason}`);
|
|
30
|
+
}
|
|
31
|
+
lines.push(`- Codex home: ${result.plan.codexHome}`);
|
|
32
|
+
lines.push(`- Skill: ${result.readiness.skill.status} (${result.readiness.skill.path})`);
|
|
33
|
+
const configLabel = result.readiness.config.status === 'invalid'
|
|
34
|
+
? `invalid (${result.readiness.config.path})`
|
|
35
|
+
: `${result.readiness.config.status} (${result.readiness.config.path})`;
|
|
36
|
+
lines.push(`- Config: ${configLabel}`);
|
|
37
|
+
if (result.readiness.config.detail) {
|
|
38
|
+
lines.push(` detail: ${result.readiness.config.detail}`);
|
|
39
|
+
}
|
|
40
|
+
if (result.readiness.config.error) {
|
|
41
|
+
lines.push(` error: ${result.readiness.config.error}`);
|
|
42
|
+
}
|
|
43
|
+
lines.push(`- Command: ${result.plan.commandLine}`);
|
|
44
|
+
lines.push('- Config snippet:');
|
|
45
|
+
for (const line of result.plan.configSnippet.split('\n')) {
|
|
46
|
+
lines.push(` ${line}`);
|
|
47
|
+
}
|
|
48
|
+
if (result.status === 'planned') {
|
|
49
|
+
lines.push('Run with --yes to apply this setup.');
|
|
50
|
+
}
|
|
51
|
+
return lines;
|
|
52
|
+
}
|
|
53
|
+
async function applyDevtoolsSetup(plan, env) {
|
|
54
|
+
await new Promise((resolve, reject) => {
|
|
55
|
+
const child = spawn(plan.command, plan.args, { stdio: 'inherit', env });
|
|
56
|
+
child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
|
|
57
|
+
child.once('exit', (code) => {
|
|
58
|
+
if (code === 0) {
|
|
59
|
+
resolve();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
reject(new Error(`codex mcp add exited with code ${code ?? 'unknown'}`));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
1
|
import process from 'node:process';
|
|
2
|
+
import { buildDevtoolsSetupPlan, DEVTOOLS_SKILL_NAME, resolveDevtoolsReadiness } from './utils/devtools.js';
|
|
5
3
|
import { resolveOptionalDependency } from './utils/optionalDeps.js';
|
|
6
4
|
const OPTIONAL_DEPENDENCIES = [
|
|
7
5
|
{
|
|
@@ -12,7 +10,6 @@ const OPTIONAL_DEPENDENCIES = [
|
|
|
12
10
|
{ name: 'pixelmatch', install: 'npm install --save-dev pixelmatch' },
|
|
13
11
|
{ name: 'cheerio', install: 'npm install --save-dev cheerio' }
|
|
14
12
|
];
|
|
15
|
-
const DEVTOOLS_SKILL_NAME = 'chrome-devtools';
|
|
16
13
|
export function runDoctor(cwd = process.cwd()) {
|
|
17
14
|
const dependencies = OPTIONAL_DEPENDENCIES.map((entry) => {
|
|
18
15
|
const resolved = resolveOptionalDependency(entry.name, cwd);
|
|
@@ -26,20 +23,34 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
26
23
|
install: entry.install
|
|
27
24
|
};
|
|
28
25
|
});
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const skillInstalled = existsSync(skillPath);
|
|
26
|
+
const readiness = resolveDevtoolsReadiness();
|
|
27
|
+
const setupPlan = buildDevtoolsSetupPlan();
|
|
32
28
|
const devtools = {
|
|
33
|
-
status:
|
|
29
|
+
status: readiness.status,
|
|
34
30
|
skill: {
|
|
35
31
|
name: DEVTOOLS_SKILL_NAME,
|
|
36
|
-
status:
|
|
37
|
-
path:
|
|
38
|
-
install:
|
|
32
|
+
status: readiness.skill.status,
|
|
33
|
+
path: readiness.skill.path,
|
|
34
|
+
install: readiness.skill.status === 'ok'
|
|
39
35
|
? undefined
|
|
40
36
|
: [
|
|
41
|
-
`Copy the ${DEVTOOLS_SKILL_NAME} skill into ${
|
|
42
|
-
`Expected file: ${
|
|
37
|
+
`Copy the ${DEVTOOLS_SKILL_NAME} skill into ${setupPlan.codexHome}/skills/${DEVTOOLS_SKILL_NAME}`,
|
|
38
|
+
`Expected file: ${readiness.skill.path}`
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
config: {
|
|
42
|
+
status: readiness.config.status,
|
|
43
|
+
path: readiness.config.path,
|
|
44
|
+
detail: readiness.config.detail,
|
|
45
|
+
error: readiness.config.error,
|
|
46
|
+
install: readiness.config.status === 'ok'
|
|
47
|
+
? undefined
|
|
48
|
+
: [
|
|
49
|
+
'Run: codex-orchestrator devtools setup',
|
|
50
|
+
`Run: ${setupPlan.commandLine}`,
|
|
51
|
+
`Config path: ${setupPlan.configPath}`,
|
|
52
|
+
'Config snippet:',
|
|
53
|
+
...setupPlan.configSnippet.split('\n')
|
|
43
54
|
]
|
|
44
55
|
},
|
|
45
56
|
enablement: [
|
|
@@ -48,9 +59,12 @@ export function runDoctor(cwd = process.cwd()) {
|
|
|
48
59
|
]
|
|
49
60
|
};
|
|
50
61
|
const missing = dependencies.filter((dep) => dep.status === 'missing').map((dep) => dep.name);
|
|
51
|
-
if (
|
|
62
|
+
if (readiness.skill.status === 'missing') {
|
|
52
63
|
missing.push(DEVTOOLS_SKILL_NAME);
|
|
53
64
|
}
|
|
65
|
+
if (readiness.config.status !== 'ok') {
|
|
66
|
+
missing.push(`${DEVTOOLS_SKILL_NAME}-config`);
|
|
67
|
+
}
|
|
54
68
|
return {
|
|
55
69
|
status: missing.length === 0 ? 'ok' : 'warning',
|
|
56
70
|
missing,
|
|
@@ -83,15 +97,26 @@ export function formatDoctorSummary(result) {
|
|
|
83
97
|
lines.push(` install: ${instruction}`);
|
|
84
98
|
}
|
|
85
99
|
}
|
|
100
|
+
if (result.devtools.config.status === 'ok') {
|
|
101
|
+
lines.push(` - config.toml: ok (${result.devtools.config.path})`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const label = result.devtools.config.status === 'invalid'
|
|
105
|
+
? `invalid (${result.devtools.config.path})`
|
|
106
|
+
: `missing (${result.devtools.config.path})`;
|
|
107
|
+
lines.push(` - config.toml: ${label}`);
|
|
108
|
+
if (result.devtools.config.detail) {
|
|
109
|
+
lines.push(` detail: ${result.devtools.config.detail}`);
|
|
110
|
+
}
|
|
111
|
+
if (result.devtools.config.error) {
|
|
112
|
+
lines.push(` error: ${result.devtools.config.error}`);
|
|
113
|
+
}
|
|
114
|
+
for (const instruction of result.devtools.config.install ?? []) {
|
|
115
|
+
lines.push(` install: ${instruction}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
86
118
|
for (const line of result.devtools.enablement) {
|
|
87
119
|
lines.push(` - ${line}`);
|
|
88
120
|
}
|
|
89
121
|
return lines;
|
|
90
122
|
}
|
|
91
|
-
function resolveCodexHome() {
|
|
92
|
-
const override = process.env.CODEX_HOME?.trim();
|
|
93
|
-
if (override) {
|
|
94
|
-
return override;
|
|
95
|
-
}
|
|
96
|
-
return join(homedir(), '.codex');
|
|
97
|
-
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { bootstrapManifest } from '../run/manifest.js';
|
|
3
3
|
import { generateRunId } from '../utils/runId.js';
|
|
4
|
-
import { JsonlWriter } from '../utils/jsonlWriter.js';
|
|
5
4
|
import { ExperienceStore } from '../../persistence/ExperienceStore.js';
|
|
6
5
|
import { createTelemetrySink } from '../../../../packages/orchestrator/src/telemetry/otel-exporter.js';
|
|
7
6
|
import { createNotificationSink } from '../../../../packages/orchestrator/src/notifications/index.js';
|
|
@@ -47,7 +46,11 @@ export async function bootstrapExecContext(context, invocation) {
|
|
|
47
46
|
targets: invocation.notifyTargets,
|
|
48
47
|
envTargets: envNotifications
|
|
49
48
|
});
|
|
50
|
-
const jsonlWriter = outputMode === 'jsonl'
|
|
49
|
+
const jsonlWriter = outputMode === 'jsonl'
|
|
50
|
+
? (event) => {
|
|
51
|
+
stdout.write(`${JSON.stringify(event)}\n`);
|
|
52
|
+
}
|
|
53
|
+
: null;
|
|
51
54
|
return {
|
|
52
55
|
env,
|
|
53
56
|
invocation,
|
|
@@ -2,9 +2,6 @@ import process from 'node:process';
|
|
|
2
2
|
import { persistManifest } from '../run/manifestPersister.js';
|
|
3
3
|
import { isoTimestamp } from '../utils/time.js';
|
|
4
4
|
import { logger } from '../../logger.js';
|
|
5
|
-
import { runLearningHarvester } from '../../learning/harvester.js';
|
|
6
|
-
import { synthesizeScenario } from '../../learning/runner.js';
|
|
7
|
-
import { runScenarioValidation } from '../../learning/validator.js';
|
|
8
5
|
export async function maybeTriggerLearning(runContext, runStatus) {
|
|
9
6
|
const enabled = process.env.LEARNING_PIPELINE_ENABLED === '1';
|
|
10
7
|
if (!enabled) {
|
|
@@ -15,6 +12,11 @@ export async function maybeTriggerLearning(runContext, runStatus) {
|
|
|
15
12
|
return;
|
|
16
13
|
}
|
|
17
14
|
try {
|
|
15
|
+
const [{ runLearningHarvester }, { synthesizeScenario }, { runScenarioValidation }] = await Promise.all([
|
|
16
|
+
import('../../learning/harvester.js'),
|
|
17
|
+
import('../../learning/runner.js'),
|
|
18
|
+
import('../../learning/validator.js')
|
|
19
|
+
]);
|
|
18
20
|
const harvester = await runLearningHarvester(runContext.manifest, {
|
|
19
21
|
repoRoot: runContext.env.repoRoot,
|
|
20
22
|
runsRoot: runContext.env.runsRoot,
|
|
@@ -10,7 +10,7 @@ export async function runExecStage(context) {
|
|
|
10
10
|
const serialized = serializeExecEvent(event);
|
|
11
11
|
context.telemetryTasks.push(Promise.resolve(context.telemetrySink.record(serialized)).then(() => undefined));
|
|
12
12
|
if (context.outputMode === 'jsonl' && context.jsonlWriter) {
|
|
13
|
-
context.jsonlWriter
|
|
13
|
+
context.jsonlWriter(serialized);
|
|
14
14
|
}
|
|
15
15
|
else if (context.outputMode === 'interactive') {
|
|
16
16
|
streamInteractive(context.stdout, context.stderr, event);
|
|
@@ -61,7 +61,7 @@ export function createRunSummaryPayload(params) {
|
|
|
61
61
|
}
|
|
62
62
|
export function renderRunOutput(context, summaryPayload, summaryEvent) {
|
|
63
63
|
if (context.outputMode === 'jsonl' && context.jsonlWriter) {
|
|
64
|
-
context.jsonlWriter
|
|
64
|
+
context.jsonlWriter(summaryEvent);
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
if (context.outputMode === 'json') {
|
|
@@ -2,8 +2,11 @@ import process from 'node:process';
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { TaskManager } from '../manager.js';
|
|
5
|
+
import { RunManifestWriter } from '../persistence/RunManifestWriter.js';
|
|
6
|
+
import { TaskStateStore } from '../persistence/TaskStateStore.js';
|
|
5
7
|
import { CommandPlanner, CommandBuilder, CommandTester, CommandReviewer } from './adapters/index.js';
|
|
6
|
-
import {
|
|
8
|
+
import { resolveEnvironmentPaths } from '../../../scripts/lib/run-manifests.js';
|
|
9
|
+
import { normalizeEnvironmentPaths } from './run/environment.js';
|
|
7
10
|
import { bootstrapManifest, loadManifest, updateHeartbeat, finalizeStatus, appendSummary, ensureGuardrailStatus, resetForResume, recordResumeEvent } from './run/manifest.js';
|
|
8
11
|
import { ManifestPersister, persistManifest } from './run/manifestPersister.js';
|
|
9
12
|
import { generateRunId } from './utils/runId.js';
|
|
@@ -18,14 +21,15 @@ import { ControlPlaneService } from './services/controlPlaneService.js';
|
|
|
18
21
|
import { SchedulerService } from './services/schedulerService.js';
|
|
19
22
|
import { applyHandlesToRunSummary, applyPrivacyToRunSummary, persistRunSummary } from './services/runSummaryWriter.js';
|
|
20
23
|
import { prepareRun, resolvePipelineForResume, overrideTaskEnvironment } from './services/runPreparation.js';
|
|
21
|
-
import { loadUserConfig } from './config/userConfig.js';
|
|
24
|
+
import { loadPackageConfig, loadUserConfig } from './config/userConfig.js';
|
|
22
25
|
import { RunEventPublisher, snapshotStages } from './events/runEvents.js';
|
|
23
26
|
import { CLI_EXECUTION_MODE_PARSER, resolveRequiresCloudPolicy } from '../utils/executionMode.js';
|
|
27
|
+
const resolveBaseEnvironment = () => normalizeEnvironmentPaths(resolveEnvironmentPaths());
|
|
24
28
|
export class CodexOrchestrator {
|
|
25
29
|
baseEnv;
|
|
26
30
|
controlPlane = new ControlPlaneService();
|
|
27
31
|
scheduler = new SchedulerService();
|
|
28
|
-
constructor(baseEnv =
|
|
32
|
+
constructor(baseEnv = resolveBaseEnvironment()) {
|
|
29
33
|
this.baseEnv = baseEnv;
|
|
30
34
|
}
|
|
31
35
|
async start(options = {}) {
|
|
@@ -77,7 +81,10 @@ export class CodexOrchestrator {
|
|
|
77
81
|
const resolver = new PipelineResolver();
|
|
78
82
|
const designConfig = await resolver.loadDesignConfig(actualEnv.repoRoot);
|
|
79
83
|
const userConfig = await loadUserConfig(actualEnv);
|
|
80
|
-
const
|
|
84
|
+
const fallbackConfig = manifest.pipeline_id === 'rlm' && userConfig?.source === 'repo'
|
|
85
|
+
? await loadPackageConfig(actualEnv)
|
|
86
|
+
: null;
|
|
87
|
+
const pipeline = resolvePipelineForResume(actualEnv, manifest, userConfig, fallbackConfig);
|
|
81
88
|
const envOverrides = resolver.resolveDesignEnvOverrides(designConfig, pipeline.id);
|
|
82
89
|
await this.validateResumeToken(paths, manifest, options.resumeToken ?? null);
|
|
83
90
|
recordResumeEvent(manifest, {
|
|
@@ -196,11 +203,13 @@ export class CodexOrchestrator {
|
|
|
196
203
|
logPath: params.paths.logPath
|
|
197
204
|
});
|
|
198
205
|
}
|
|
199
|
-
createTaskManager(runId, pipeline, executePipeline, getResult, plannerInstance) {
|
|
206
|
+
createTaskManager(runId, pipeline, executePipeline, getResult, plannerInstance, env) {
|
|
200
207
|
const planner = plannerInstance ?? new CommandPlanner(pipeline);
|
|
201
208
|
const builder = new CommandBuilder(executePipeline);
|
|
202
209
|
const tester = new CommandTester(getResult);
|
|
203
210
|
const reviewer = new CommandReviewer(getResult);
|
|
211
|
+
const stateStore = new TaskStateStore({ outDir: env.outRoot, runsDir: env.runsRoot });
|
|
212
|
+
const manifestWriter = new RunManifestWriter({ runsDir: env.runsRoot });
|
|
204
213
|
const options = {
|
|
205
214
|
planner,
|
|
206
215
|
builder,
|
|
@@ -208,7 +217,7 @@ export class CodexOrchestrator {
|
|
|
208
217
|
reviewer,
|
|
209
218
|
runIdFactory: () => runId,
|
|
210
219
|
modePolicy: (task, subtask) => this.determineMode(task, subtask),
|
|
211
|
-
persistence: { autoStart: true }
|
|
220
|
+
persistence: { autoStart: true, stateStore, manifestWriter }
|
|
212
221
|
};
|
|
213
222
|
return new TaskManager(options);
|
|
214
223
|
}
|
|
@@ -445,7 +454,7 @@ export class CodexOrchestrator {
|
|
|
445
454
|
return executing;
|
|
446
455
|
};
|
|
447
456
|
const getResult = () => pipelineResult;
|
|
448
|
-
const manager = this.createTaskManager(runId, pipeline, executePipeline, getResult, planner);
|
|
457
|
+
const manager = this.createTaskManager(runId, pipeline, executePipeline, getResult, planner, env);
|
|
449
458
|
this.attachPlanTargetTracker(manager, manifest, paths, persister);
|
|
450
459
|
getPrivacyGuard().reset();
|
|
451
460
|
const controlPlaneResult = await this.controlPlane.guard({
|
|
@@ -1,34 +1,23 @@
|
|
|
1
|
-
import { defaultDiagnosticsPipeline } from './defaultDiagnostics.js';
|
|
2
|
-
import { designReferencePipeline } from './designReference.js';
|
|
3
|
-
import { hiFiDesignToolkitPipeline } from './hiFiDesignToolkit.js';
|
|
4
1
|
import { findPipeline } from '../config/userConfig.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[designReferencePipeline.id, designReferencePipeline],
|
|
8
|
-
[hiFiDesignToolkitPipeline.id, hiFiDesignToolkitPipeline]
|
|
9
|
-
]);
|
|
10
|
-
function getBuiltinPipeline(id) {
|
|
11
|
-
if (!id) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
return builtinPipelines.get(id) ?? null;
|
|
2
|
+
function resolveConfigSource(config) {
|
|
3
|
+
return config?.source === 'package' ? 'default' : 'user';
|
|
15
4
|
}
|
|
16
|
-
export function resolvePipeline(
|
|
5
|
+
export function resolvePipeline(_env, options) {
|
|
17
6
|
const { pipelineId, config } = options;
|
|
7
|
+
const configSource = resolveConfigSource(config);
|
|
18
8
|
if (pipelineId) {
|
|
19
9
|
const fromUser = findPipeline(config, pipelineId);
|
|
20
10
|
if (fromUser) {
|
|
21
|
-
return { pipeline: fromUser, source:
|
|
22
|
-
}
|
|
23
|
-
const builtin = getBuiltinPipeline(pipelineId);
|
|
24
|
-
if (builtin) {
|
|
25
|
-
return { pipeline: builtin, source: 'default' };
|
|
11
|
+
return { pipeline: fromUser, source: configSource };
|
|
26
12
|
}
|
|
27
|
-
|
|
13
|
+
const suffix = config ? '' : ' (missing codex.orchestrator.json)';
|
|
14
|
+
throw new Error(`Pipeline '${pipelineId}' not found${suffix}.`);
|
|
28
15
|
}
|
|
29
|
-
const defaultId = config?.defaultPipeline ??
|
|
16
|
+
const defaultId = config?.defaultPipeline ?? 'diagnostics';
|
|
30
17
|
const userPipeline = findPipeline(config, defaultId);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
if (userPipeline) {
|
|
19
|
+
return { pipeline: userPipeline, source: configSource };
|
|
20
|
+
}
|
|
21
|
+
const suffix = config ? '' : ' (missing codex.orchestrator.json)';
|
|
22
|
+
throw new Error(`Pipeline '${defaultId}' not found${suffix}.`);
|
|
34
23
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
export function buildRlmPrompt(input) {
|
|
3
|
+
const repoName = basename(input.repoRoot) || 'repo';
|
|
4
|
+
const maxIterations = input.maxIterations === 0 ? 'unlimited' : String(input.maxIterations);
|
|
5
|
+
const lines = [
|
|
6
|
+
`You are Codex running an RLM loop in repo "${repoName}".`,
|
|
7
|
+
`Goal: ${input.goal}`,
|
|
8
|
+
`Iteration: ${input.iteration} of ${maxIterations}.`
|
|
9
|
+
];
|
|
10
|
+
if (input.diffSummary) {
|
|
11
|
+
lines.push('', 'Workspace summary:', input.diffSummary.trim());
|
|
12
|
+
}
|
|
13
|
+
if (input.lastValidatorOutput) {
|
|
14
|
+
lines.push('', 'Last validator output:', input.lastValidatorOutput.trim());
|
|
15
|
+
}
|
|
16
|
+
if (input.validatorCommand) {
|
|
17
|
+
lines.push('', `Validator command (do NOT run it): ${input.validatorCommand}`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
lines.push('', 'Validator: none (budgeted run)');
|
|
21
|
+
}
|
|
22
|
+
lines.push('', 'Instructions:', '- Plan and apply minimal changes toward the goal.', '- Use tools as needed (edit files, run commands, inspect diffs).', '- Do not run the validator command; it will be run after you finish.', '- Self-refine before finalizing (ensure changes align with goal).');
|
|
23
|
+
if (input.roles === 'triad') {
|
|
24
|
+
if (input.subagentsEnabled) {
|
|
25
|
+
lines.push('', 'Use subagents if available: Planner, Critic, Reviser.');
|
|
26
|
+
}
|
|
27
|
+
lines.push('', 'Role split (single response with sections):', 'Planner: outline the plan.', 'Critic: identify risks or missing steps.', 'Reviser: execute the plan and summarize changes.');
|
|
28
|
+
}
|
|
29
|
+
lines.push('', 'End your response with:', 'Summary: <one-line summary of changes>', 'Next: <what to try next if validator still fails>');
|
|
30
|
+
return lines.join('\n');
|
|
31
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { isoTimestamp } from '../utils/time.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const MAX_DIFF_CHARS = 2000;
|
|
8
|
+
const MAX_VALIDATOR_OUTPUT_CHARS = 4000;
|
|
9
|
+
function truncate(value, maxChars) {
|
|
10
|
+
if (value.length <= maxChars) {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
return `${value.slice(0, maxChars)}...`;
|
|
14
|
+
}
|
|
15
|
+
function extractSummary(output) {
|
|
16
|
+
const lines = output
|
|
17
|
+
.split('\n')
|
|
18
|
+
.map((line) => line.trim())
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
21
|
+
const line = lines[i] ?? '';
|
|
22
|
+
if (line.toLowerCase().startsWith('summary:')) {
|
|
23
|
+
return line.slice('summary:'.length).trim() || null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function summarizeValidatorOutput(result) {
|
|
29
|
+
const combined = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join('\n');
|
|
30
|
+
if (!combined) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return truncate(combined, MAX_VALIDATOR_OUTPUT_CHARS);
|
|
34
|
+
}
|
|
35
|
+
async function collectGitSummary(repoRoot) {
|
|
36
|
+
try {
|
|
37
|
+
const [statusResult, diffResult] = await Promise.all([
|
|
38
|
+
execFileAsync('git', ['status', '--short'], { cwd: repoRoot }),
|
|
39
|
+
execFileAsync('git', ['diff', '--stat'], { cwd: repoRoot })
|
|
40
|
+
]);
|
|
41
|
+
const statusText = statusResult.stdout.trim() || 'clean';
|
|
42
|
+
const diffText = diffResult.stdout.trim() || 'no diff';
|
|
43
|
+
return [
|
|
44
|
+
'status:',
|
|
45
|
+
truncate(statusText, MAX_DIFF_CHARS),
|
|
46
|
+
'diff:',
|
|
47
|
+
truncate(diffText, MAX_DIFF_CHARS)
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
return `git summary unavailable: ${message}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function writeStateFile(path, state) {
|
|
56
|
+
await writeFile(path, JSON.stringify(state, null, 2), 'utf8');
|
|
57
|
+
}
|
|
58
|
+
async function writeValidatorLog(path, command, result) {
|
|
59
|
+
const lines = [];
|
|
60
|
+
lines.push(`[${isoTimestamp()}] $ ${command}`);
|
|
61
|
+
if (result.spawnError) {
|
|
62
|
+
lines.push('[validator] spawn error');
|
|
63
|
+
}
|
|
64
|
+
if (result.stdout.trim()) {
|
|
65
|
+
lines.push(result.stdout.trimEnd());
|
|
66
|
+
}
|
|
67
|
+
if (result.stderr.trim()) {
|
|
68
|
+
lines.push(result.stderr.trimEnd());
|
|
69
|
+
}
|
|
70
|
+
lines.push(`[exit] ${result.exitCode}`);
|
|
71
|
+
await writeFile(path, `${lines.join('\n')}\n`, 'utf8');
|
|
72
|
+
}
|
|
73
|
+
export async function runRlmLoop(options) {
|
|
74
|
+
const now = options.now ?? isoTimestamp;
|
|
75
|
+
const log = options.logger ?? (() => undefined);
|
|
76
|
+
const state = {
|
|
77
|
+
goal: options.goal,
|
|
78
|
+
validator: options.validatorCommand ?? 'none',
|
|
79
|
+
roles: options.roles,
|
|
80
|
+
maxIterations: options.maxIterations,
|
|
81
|
+
maxMinutes: options.maxMinutes ?? null,
|
|
82
|
+
iterations: []
|
|
83
|
+
};
|
|
84
|
+
const runDir = options.runDir;
|
|
85
|
+
const statePath = join(runDir, 'state.json');
|
|
86
|
+
await mkdir(runDir, { recursive: true });
|
|
87
|
+
const collectSummary = options.collectDiffSummary ?? collectGitSummary;
|
|
88
|
+
const maxIterations = options.maxIterations;
|
|
89
|
+
const maxMinutes = options.maxMinutes ?? null;
|
|
90
|
+
const startTime = Date.now();
|
|
91
|
+
const deadline = maxMinutes && maxMinutes > 0 ? startTime + maxMinutes * 60 * 1000 : null;
|
|
92
|
+
if (maxIterations === 0 && !options.validatorCommand && !deadline) {
|
|
93
|
+
state.final = { status: 'invalid_config', exitCode: 5 };
|
|
94
|
+
await writeStateFile(statePath, state);
|
|
95
|
+
return { state, exitCode: 5, error: 'validator none with unbounded budget' };
|
|
96
|
+
}
|
|
97
|
+
const timeExceeded = () => deadline !== null && Date.now() >= deadline;
|
|
98
|
+
let lastValidatorOutput = null;
|
|
99
|
+
const finalize = async (status) => {
|
|
100
|
+
state.final = status ?? { status: 'error', exitCode: 10 };
|
|
101
|
+
await writeStateFile(statePath, state);
|
|
102
|
+
return { state, exitCode: state.final.exitCode };
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
for (let iteration = 1; maxIterations === 0 || iteration <= maxIterations; iteration += 1) {
|
|
106
|
+
if (timeExceeded()) {
|
|
107
|
+
if (options.validatorCommand) {
|
|
108
|
+
return await finalize({ status: 'max_minutes', exitCode: 3 });
|
|
109
|
+
}
|
|
110
|
+
return await finalize({ status: 'budget_complete', exitCode: 0 });
|
|
111
|
+
}
|
|
112
|
+
const iterationStartedAt = now();
|
|
113
|
+
const preDiffSummary = await collectSummary(options.repoRoot);
|
|
114
|
+
const agentResult = await options.runAgent({
|
|
115
|
+
goal: options.goal,
|
|
116
|
+
iteration,
|
|
117
|
+
maxIterations,
|
|
118
|
+
roles: options.roles,
|
|
119
|
+
subagentsEnabled: options.subagentsEnabled,
|
|
120
|
+
validatorCommand: options.validatorCommand,
|
|
121
|
+
lastValidatorOutput,
|
|
122
|
+
diffSummary: preDiffSummary,
|
|
123
|
+
repoRoot: options.repoRoot
|
|
124
|
+
});
|
|
125
|
+
const postDiffSummary = await collectSummary(options.repoRoot);
|
|
126
|
+
const summary = agentResult.summary ?? extractSummary(agentResult.output) ?? null;
|
|
127
|
+
let validatorExitCode = null;
|
|
128
|
+
let validatorLogPath = null;
|
|
129
|
+
let validatorResult = null;
|
|
130
|
+
if (options.validatorCommand) {
|
|
131
|
+
if (!options.runValidator) {
|
|
132
|
+
throw new Error('Validator runner missing');
|
|
133
|
+
}
|
|
134
|
+
const validatorLogFile = join(runDir, `validator-${iteration}.log`);
|
|
135
|
+
validatorResult = await options.runValidator(options.validatorCommand);
|
|
136
|
+
await writeValidatorLog(validatorLogFile, options.validatorCommand, validatorResult);
|
|
137
|
+
validatorLogPath = relative(options.repoRoot, validatorLogFile);
|
|
138
|
+
validatorExitCode = validatorResult.exitCode;
|
|
139
|
+
lastValidatorOutput = summarizeValidatorOutput(validatorResult);
|
|
140
|
+
}
|
|
141
|
+
state.iterations.push({
|
|
142
|
+
n: iteration,
|
|
143
|
+
startedAt: iterationStartedAt,
|
|
144
|
+
summary,
|
|
145
|
+
validatorExitCode,
|
|
146
|
+
validatorLogPath,
|
|
147
|
+
diffSummary: postDiffSummary
|
|
148
|
+
});
|
|
149
|
+
await writeStateFile(statePath, state);
|
|
150
|
+
if (validatorResult?.spawnError) {
|
|
151
|
+
return await finalize({ status: 'error', exitCode: 4 });
|
|
152
|
+
}
|
|
153
|
+
if (validatorExitCode === 0) {
|
|
154
|
+
return await finalize({ status: 'passed', exitCode: 0 });
|
|
155
|
+
}
|
|
156
|
+
if (maxIterations > 0 && iteration >= maxIterations) {
|
|
157
|
+
if (options.validatorCommand) {
|
|
158
|
+
return await finalize({ status: 'max_iterations', exitCode: 3 });
|
|
159
|
+
}
|
|
160
|
+
return await finalize({ status: 'budget_complete', exitCode: 0 });
|
|
161
|
+
}
|
|
162
|
+
if (timeExceeded()) {
|
|
163
|
+
if (options.validatorCommand) {
|
|
164
|
+
return await finalize({ status: 'max_minutes', exitCode: 3 });
|
|
165
|
+
}
|
|
166
|
+
return await finalize({ status: 'budget_complete', exitCode: 0 });
|
|
167
|
+
}
|
|
168
|
+
log(`RLM iteration ${iteration} complete; continuing.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
173
|
+
await finalize({ status: 'error', exitCode: 10 });
|
|
174
|
+
return { state, exitCode: 10, error: message };
|
|
175
|
+
}
|
|
176
|
+
return await finalize({ status: 'error', exitCode: 10 });
|
|
177
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|