@kbediako/codex-orchestrator 0.1.15 → 0.1.16
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
CHANGED
|
@@ -80,6 +80,11 @@ codex -c 'mcp_servers.delegation.enabled=true' ...
|
|
|
80
80
|
```
|
|
81
81
|
`delegate-server` is the canonical name; `delegation-server` is supported as an alias (older docs may use it).
|
|
82
82
|
|
|
83
|
+
Delegation guard profile:
|
|
84
|
+
- `CODEX_ORCHESTRATOR_GUARD_PROFILE=auto` (default): strict in CO-style repos, warn in lightweight repos.
|
|
85
|
+
- Set `CODEX_ORCHESTRATOR_GUARD_PROFILE=warn` for ad-hoc/no-task-id runs.
|
|
86
|
+
- Set `CODEX_ORCHESTRATOR_GUARD_PROFILE=strict` to enforce full delegation evidence checks.
|
|
87
|
+
|
|
83
88
|
## Delegation + RLM flow
|
|
84
89
|
|
|
85
90
|
RLM (Recursive Language Model) is the long-horizon loop used by the `rlm` pipeline (`codex-orchestrator rlm "<goal>"` or `codex-orchestrator start rlm --goal "<goal>"`). Delegated runs only enter RLM when the child is launched with the `rlm` pipeline (or the rlm runner directly). In auto mode it resolves to symbolic when delegated, when `RLM_CONTEXT_PATH` is set, or when the context exceeds `RLM_SYMBOLIC_MIN_BYTES`; otherwise it stays iterative. The runner writes state to `.runs/<task-id>/cli/<run-id>/rlm/state.json` and stops when the validator passes or budgets are exhausted.
|
|
@@ -40,6 +40,7 @@ const CONFIRMATION_ERROR_CODES = new Set([
|
|
|
40
40
|
'nonce_already_consumed'
|
|
41
41
|
]);
|
|
42
42
|
const TOOL_PROFILE_ENTRY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
43
|
+
const TERMINAL_RUN_STATUSES = new Set(['succeeded', 'failed', 'cancelled', 'canceled']);
|
|
43
44
|
export async function startDelegationServer(options) {
|
|
44
45
|
const repoRoot = resolve(options.repoRoot);
|
|
45
46
|
const configFiles = await loadDelegationConfigFiles({ repoRoot });
|
|
@@ -272,7 +273,9 @@ async function handleDelegateStatus(input, allowedRoots, allowedHosts) {
|
|
|
272
273
|
const raw = await readFile(manifestPath, 'utf8');
|
|
273
274
|
const manifest = JSON.parse(raw);
|
|
274
275
|
const eventsPath = resolve(dirname(manifestPath), 'events.jsonl');
|
|
275
|
-
|
|
276
|
+
if (!TERMINAL_RUN_STATUSES.has(manifest.status)) {
|
|
277
|
+
await assertControlEndpoint(manifestPath, allowedHosts);
|
|
278
|
+
}
|
|
276
279
|
return {
|
|
277
280
|
run_id: manifest.run_id,
|
|
278
281
|
task_id: manifest.task_id,
|
|
@@ -153,6 +153,7 @@ export async function runCommandStage(context, hooks = {}) {
|
|
|
153
153
|
const baseEnv = {
|
|
154
154
|
...process.env,
|
|
155
155
|
...(envOverrides ?? {}),
|
|
156
|
+
MCP_RUNNER_TASK_ID: manifest.task_id,
|
|
156
157
|
CODEX_ORCHESTRATOR_TASK_ID: manifest.task_id,
|
|
157
158
|
CODEX_ORCHESTRATOR_RUN_ID: manifest.run_id,
|
|
158
159
|
CODEX_ORCHESTRATOR_PIPELINE_ID: manifest.pipeline_id,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { logger } from '../../logger.js';
|
|
7
|
+
import { resolveEnvironmentPaths } from '../../../../scripts/lib/run-manifests.js';
|
|
8
|
+
const DEFAULT_PROFILE = 'auto';
|
|
9
|
+
const REPO_STRICT_MARKERS = ['AGENTS.md', join('tasks', 'index.json'), join('docs', 'TASKS.md')];
|
|
10
|
+
export function parseGuardProfile(raw) {
|
|
11
|
+
const normalized = String(raw ?? '').trim().toLowerCase();
|
|
12
|
+
if (normalized === 'strict' || normalized === 'warn' || normalized === 'auto') {
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
return DEFAULT_PROFILE;
|
|
16
|
+
}
|
|
17
|
+
export function hasRepoStrictMarkers(repoRoot, fileExists = existsSync) {
|
|
18
|
+
return REPO_STRICT_MARKERS.every((marker) => fileExists(join(repoRoot, marker)));
|
|
19
|
+
}
|
|
20
|
+
export function resolveEffectiveGuardProfile(repoRoot, env = process.env, fileExists = existsSync) {
|
|
21
|
+
const requested = parseGuardProfile(env.CODEX_ORCHESTRATOR_GUARD_PROFILE ?? env.CODEX_GUARD_PROFILE);
|
|
22
|
+
if (requested === 'strict' || requested === 'warn') {
|
|
23
|
+
return requested;
|
|
24
|
+
}
|
|
25
|
+
return hasRepoStrictMarkers(repoRoot, fileExists) ? 'strict' : 'warn';
|
|
26
|
+
}
|
|
27
|
+
export function buildDelegationGuardEnv(env, profile) {
|
|
28
|
+
const taskId = (env.MCP_RUNNER_TASK_ID ?? '').trim();
|
|
29
|
+
const existingOverride = (env.DELEGATION_GUARD_OVERRIDE_REASON ?? '').trim();
|
|
30
|
+
if (profile !== 'warn' || taskId || existingOverride) {
|
|
31
|
+
return { ...env };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
...env,
|
|
35
|
+
DELEGATION_GUARD_OVERRIDE_REASON: 'No MCP_RUNNER_TASK_ID provided (warn profile): delegation evidence check bypassed.'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function runDelegationGuardRunner(argv = process.argv.slice(2)) {
|
|
39
|
+
const { repoRoot } = resolveEnvironmentPaths();
|
|
40
|
+
const guardPath = join(repoRoot, 'scripts', 'delegation-guard.mjs');
|
|
41
|
+
const profile = resolveEffectiveGuardProfile(repoRoot);
|
|
42
|
+
if (!existsSync(guardPath)) {
|
|
43
|
+
if (profile === 'strict') {
|
|
44
|
+
logger.error(`[delegation-guard] failed: ${guardPath} not found (strict profile)`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
logger.warn(`[delegation-guard] skipped: ${guardPath} not found (warn profile)`);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const child = spawn(process.execPath, [guardPath, ...argv], {
|
|
53
|
+
stdio: 'inherit',
|
|
54
|
+
env: buildDelegationGuardEnv(process.env, profile)
|
|
55
|
+
});
|
|
56
|
+
child.on('error', (error) => {
|
|
57
|
+
logger.error(`[delegation-guard] failed: ${error.message}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
child.on('exit', (code, signal) => {
|
|
61
|
+
if (typeof code === 'number') {
|
|
62
|
+
process.exit(code);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (signal) {
|
|
66
|
+
logger.error(`[delegation-guard] exited with signal ${signal}`);
|
|
67
|
+
}
|
|
68
|
+
process.exit(1);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function isDirectExecution() {
|
|
72
|
+
const entry = process.argv[1];
|
|
73
|
+
if (!entry) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return import.meta.url === pathToFileURL(entry).href;
|
|
77
|
+
}
|
|
78
|
+
if (isDirectExecution()) {
|
|
79
|
+
void runDelegationGuardRunner();
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -24,7 +24,9 @@ Use this skill when a task can be split into parallel streams or when the main c
|
|
|
24
24
|
## Required conventions
|
|
25
25
|
- Use `MCP_RUNNER_TASK_ID=<task-id>-<stream>` for subagents.
|
|
26
26
|
- Record manifest paths and summarize findings in the main run.
|
|
27
|
-
-
|
|
27
|
+
- Before review handoff, run the delegation guard stage via the packaged runner:
|
|
28
|
+
`node "$CODEX_ORCHESTRATOR_PACKAGE_ROOT/dist/orchestrator/src/cli/utils/delegationGuardRunner.js"`.
|
|
29
|
+
For ad-hoc runs without task IDs, set `CODEX_ORCHESTRATOR_GUARD_PROFILE=warn`.
|
|
28
30
|
|
|
29
31
|
## Minimal delegation workflow
|
|
30
32
|
1) Name streams and write 1–2 sentence goals for each.
|