@kbediako/codex-orchestrator 0.1.22 → 0.1.23

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
@@ -152,11 +152,16 @@ Bundled skills (may vary by release):
152
152
 
153
153
  ## DevTools readiness
154
154
 
155
- Check DevTools readiness (skill + MCP config):
155
+ Check readiness (deps + capability wiring):
156
156
  ```bash
157
157
  codex-orchestrator doctor --format json
158
158
  ```
159
159
 
160
+ Usage snapshot (scans local `.runs/`):
161
+ ```bash
162
+ codex-orchestrator doctor --usage
163
+ ```
164
+
160
165
  Print DevTools MCP setup guidance:
161
166
  ```bash
162
167
  codex-orchestrator devtools setup
@@ -171,6 +176,7 @@ codex-orchestrator devtools setup
171
176
  - `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
177
  - `codex-orchestrator init codex --codex-cli --yes --codex-download-url <url> --codex-download-sha256 <sha>` — opt-in to a prebuilt Codex CLI download.
173
178
  - `codex-orchestrator codex setup` — plan/apply a CO-managed Codex CLI install (optional managed/pinned path; use `--download-url` + `--download-sha256` for prebuilts).
179
+ - `codex-orchestrator delegation setup --yes` — configure delegation MCP server wiring.
174
180
  - `codex-orchestrator self-check --format json` — JSON health payload.
175
181
  - `codex-orchestrator mcp serve` — Codex MCP stdio server.
176
182
 
@@ -14,8 +14,10 @@ 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';
20
+ import { formatDelegationSetupSummary, runDelegationSetup } from '../orchestrator/src/cli/delegationSetup.js';
19
21
  import { formatSkillsInstallSummary, installSkills } 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';
@@ -85,6 +87,9 @@ async function main() {
85
87
  case 'delegation-server':
86
88
  await handleDelegationServer(args);
87
89
  break;
90
+ case 'delegation':
91
+ await handleDelegation(args);
92
+ break;
88
93
  case 'version':
89
94
  printVersion();
90
95
  break;
@@ -332,6 +337,16 @@ async function handleRlm(orchestrator, rawArgs) {
332
337
  process.env.MCP_RUNNER_TASK_ID = taskId;
333
338
  applyRlmEnvOverrides(flags, goal);
334
339
  console.log(`Task: ${taskId}`);
340
+ const collabUserChoice = flags['collab'] !== undefined || process.env.RLM_SYMBOLIC_COLLAB !== undefined;
341
+ if (!collabUserChoice) {
342
+ const doctor = runDoctor();
343
+ if (doctor.collab.status === 'ok') {
344
+ console.log('Tip: collab is enabled. Try: codex-orchestrator rlm --collab auto \"<goal>\"');
345
+ }
346
+ else if (doctor.collab.status === 'disabled') {
347
+ console.log('Tip: collab is available but disabled. Enable with: codex features enable collab');
348
+ }
349
+ }
335
350
  let startResult = null;
336
351
  await withRunUi(flags, 'text', async (runEvents) => {
337
352
  startResult = await orchestrator.start({
@@ -544,15 +559,38 @@ async function handleInit(rawArgs) {
544
559
  async function handleDoctor(rawArgs) {
545
560
  const { flags } = parseArgs(rawArgs);
546
561
  const format = flags['format'] === 'json' ? 'json' : 'text';
547
- const result = runDoctor();
562
+ const includeUsage = Boolean(flags['usage']);
563
+ const windowDaysRaw = readStringFlag(flags, 'window-days');
564
+ let windowDays = undefined;
565
+ if (windowDaysRaw) {
566
+ if (!/^\d+$/u.test(windowDaysRaw)) {
567
+ throw new Error(`Invalid --window-days value '${windowDaysRaw}'. Expected a positive integer.`);
568
+ }
569
+ const parsed = Number(windowDaysRaw);
570
+ if (!Number.isInteger(parsed) || parsed <= 0) {
571
+ throw new Error(`Invalid --window-days value '${windowDaysRaw}'. Expected a positive integer.`);
572
+ }
573
+ windowDays = parsed;
574
+ }
575
+ const taskFilter = readStringFlag(flags, 'task') ?? null;
576
+ const doctorResult = runDoctor();
577
+ const usageResult = includeUsage ? await runDoctorUsage({ windowDays, taskFilter }) : null;
548
578
  if (format === 'json') {
549
- console.log(JSON.stringify(result, null, 2));
579
+ if (usageResult) {
580
+ console.log(JSON.stringify({ ...doctorResult, usage: usageResult }, null, 2));
581
+ return;
582
+ }
583
+ console.log(JSON.stringify(doctorResult, null, 2));
550
584
  return;
551
585
  }
552
- const summary = formatDoctorSummary(result);
553
- for (const line of summary) {
586
+ for (const line of formatDoctorSummary(doctorResult)) {
554
587
  console.log(line);
555
588
  }
589
+ if (usageResult) {
590
+ for (const line of formatDoctorUsageSummary(usageResult)) {
591
+ console.log(line);
592
+ }
593
+ }
556
594
  }
557
595
  async function handleDevtools(rawArgs) {
558
596
  const { positionals, flags } = parseArgs(rawArgs);
@@ -578,6 +616,30 @@ async function handleDevtools(rawArgs) {
578
616
  console.log(line);
579
617
  }
580
618
  }
619
+ async function handleDelegation(rawArgs) {
620
+ const { positionals, flags } = parseArgs(rawArgs);
621
+ const subcommand = positionals.shift();
622
+ if (!subcommand) {
623
+ throw new Error('delegation requires a subcommand (setup).');
624
+ }
625
+ if (subcommand !== 'setup') {
626
+ throw new Error(`Unknown delegation subcommand: ${subcommand}`);
627
+ }
628
+ const format = flags['format'] === 'json' ? 'json' : 'text';
629
+ const apply = Boolean(flags['yes']);
630
+ if (format === 'json' && apply) {
631
+ throw new Error('delegation setup does not support --format json with --yes.');
632
+ }
633
+ const repoRoot = readStringFlag(flags, 'repo') ?? process.cwd();
634
+ const result = await runDelegationSetup({ apply, repoRoot });
635
+ if (format === 'json') {
636
+ console.log(JSON.stringify(result, null, 2));
637
+ return;
638
+ }
639
+ for (const line of formatDelegationSetupSummary(result)) {
640
+ console.log(line);
641
+ }
642
+ }
581
643
  async function handleCodex(rawArgs) {
582
644
  const { positionals, flags } = parseArgs(rawArgs);
583
645
  const subcommand = positionals.shift();
@@ -961,7 +1023,11 @@ Commands:
961
1023
  --codex-download-sha256 <sha> Expected SHA256 for the prebuilt download.
962
1024
  --codex-force Overwrite existing CO-managed codex binary.
963
1025
  --yes Apply codex CLI setup (otherwise plan only).
964
- doctor [--format json]
1026
+ doctor [--format json] [--usage] [--window-days <n>] [--task <id>]
1027
+ --usage Include a local usage snapshot (scans .runs/).
1028
+ --window-days <n> Window for --usage (default 30).
1029
+ --task <id> Limit --usage scan to a specific task directory.
1030
+ --format json Emit machine-readable output.
965
1031
  codex setup
966
1032
  --source <path> Build from local Codex repo (or git URL).
967
1033
  --ref <ref> Git ref (branch/tag/sha) when building from repo.
@@ -973,6 +1039,10 @@ Commands:
973
1039
  devtools setup Print DevTools MCP setup instructions.
974
1040
  --yes Apply setup by running "codex mcp add ...".
975
1041
  --format json Emit machine-readable output (dry-run only).
1042
+ delegation setup Configure delegation MCP server wiring.
1043
+ --repo <path> Repo root for delegation server (default cwd).
1044
+ --yes Apply setup by running "codex mcp add ...".
1045
+ --format json Emit machine-readable output (dry-run only).
976
1046
  skills install Install bundled skills into $CODEX_HOME/skills.
977
1047
  --force Overwrite existing skill files.
978
1048
  --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
+ }
@@ -115,7 +115,8 @@ export function runDoctor(cwd = process.cwd()) {
115
115
  status: delegationStatus,
116
116
  config: delegationConfig,
117
117
  enablement: [
118
- `Run: codex mcp add delegation -- codex-orchestrator delegate-server --repo ${cwd}`,
118
+ 'Run: codex-orchestrator delegation setup --yes',
119
+ 'Or manually: codex mcp add delegation -- codex-orchestrator delegate-server',
119
120
  "Enable for a run with: codex -c 'mcp_servers.delegation.enabled=true' ...",
120
121
  'See: codex-orchestrator init codex'
121
122
  ]
@@ -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(` - codex mcp add delegation -- codex-orchestrator delegate-server --repo ${cwd}`);
64
+ lines.push(' - codex-orchestrator delegation setup --yes');
65
65
  lines.push(' - codex-orchestrator codex setup # optional: managed/pinned Codex CLI (stock CLI works by default)');
66
66
  return lines;
67
67
  }
package/docs/README.md CHANGED
@@ -101,8 +101,9 @@ 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 doctor [--format json]`: check optional tooling dependencies plus collab/cloud/delegation readiness and print enablement commands.
104
+ - `codex-orchestrator doctor [--format json] [--usage]`: check optional tooling dependencies plus collab/cloud/delegation readiness and print enablement commands. `--usage` appends a local usage snapshot (scans `.runs/`).
105
105
  - `codex-orchestrator devtools setup [--yes]`: print DevTools MCP setup instructions (`--yes` applies `codex mcp add ...`).
106
+ - `codex-orchestrator delegation setup [--yes]`: configure delegation MCP wiring (`--yes` applies `codex mcp add ...`).
106
107
  - `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
108
  - `codex-orchestrator self-check --format json`: emit a safe JSON health payload for smoke tests.
108
109
  - `codex-orchestrator --version`: print the package version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbediako/codex-orchestrator",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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 --repo /path/to/repo
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 --repo /path/to/repo
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` to the MCP args when registering the server or when you need repo-scoped config.
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 --repo /path/to/repo`
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 --repo /path/to/repo`
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,10 @@ 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 delegation setup --yes`
68
+ - This wraps `codex mcp add delegation ...` and keeps wiring discoverable via `codex-orchestrator doctor`.
67
69
  - `codex mcp add delegation -- codex-orchestrator delegate-server`
68
- - Optional (recommended for repo-scoped config): append `--repo /path/to/repo` to the args.
70
+ - Optional: append `--repo /path/to/repo` to pin the server to one repo (not recommended if you work across repos).
69
71
  - `delegate-server` is the canonical name; `delegation-server` is supported as an alias.
70
72
  - Per-run `-c 'mcp_servers.delegation.enabled=true'` only works **after** registration.
71
73
  - If `delegate.*` tools are missing mid-task, start a new run with: