@kbediako/codex-orchestrator 0.1.32 → 0.1.34
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 +96 -12
- package/codex.orchestrator.json +448 -0
- package/dist/bin/codex-orchestrator.js +703 -136
- package/dist/orchestrator/src/cli/codexCliSetup.js +1 -0
- package/dist/orchestrator/src/cli/config/repoConfigPolicy.js +22 -0
- package/dist/orchestrator/src/cli/config/userConfig.js +20 -9
- package/dist/orchestrator/src/cli/delegationSetup.js +111 -14
- package/dist/orchestrator/src/cli/doctor.js +264 -8
- package/dist/orchestrator/src/cli/doctorIssueLog.js +350 -0
- package/dist/orchestrator/src/cli/doctorUsage.js +150 -8
- package/dist/orchestrator/src/cli/init.js +24 -1
- package/dist/orchestrator/src/cli/mcpEnable.js +392 -0
- package/dist/orchestrator/src/cli/orchestrator.js +180 -5
- package/dist/orchestrator/src/cli/rlmRunner.js +289 -35
- package/dist/orchestrator/src/cli/run/manifest.js +31 -6
- package/dist/orchestrator/src/cli/services/commandRunner.js +10 -2
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +70 -18
- package/dist/orchestrator/src/cli/services/runPreparation.js +2 -0
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
- package/dist/orchestrator/src/cli/skills.js +3 -8
- package/dist/orchestrator/src/cli/utils/advancedAutopilot.js +114 -0
- package/dist/orchestrator/src/cli/utils/codexCli.js +21 -0
- package/dist/orchestrator/src/cli/utils/commandPreview.js +10 -0
- package/dist/orchestrator/src/cli/utils/delegationGuardRunner.js +85 -8
- package/dist/orchestrator/src/cli/utils/devtools.js +2 -1
- package/dist/orchestrator/src/cli/utils/specGuardRunner.js +79 -19
- package/dist/orchestrator/src/cloud/CodexCloudTaskExecutor.js +46 -6
- package/dist/orchestrator/src/control-plane/request-builder.js +9 -8
- package/dist/scripts/lib/pr-watch-merge.js +367 -3
- package/docs/README.md +17 -11
- package/package.json +2 -1
- package/schemas/manifest.json +27 -0
- package/skills/collab-deliberation/SKILL.md +6 -0
- package/skills/collab-evals/SKILL.md +4 -0
- package/skills/collab-subagents-first/SKILL.md +29 -7
- package/skills/delegation-usage/DELEGATION_GUIDE.md +31 -5
- package/skills/delegation-usage/SKILL.md +29 -4
- package/skills/elegance-review/SKILL.md +14 -3
- package/skills/standalone-review/SKILL.md +8 -2
- package/templates/README.md +1 -1
- package/templates/codex/AGENTS.md +12 -1
|
@@ -33,7 +33,16 @@ const DEFAULT_MAX_CONCURRENCY = 4;
|
|
|
33
33
|
const DEFAULT_SYMBOLIC_DELIBERATION_INTERVAL = 2;
|
|
34
34
|
const DEFAULT_SYMBOLIC_DELIBERATION_MAX_RUNS = 12;
|
|
35
35
|
const DEFAULT_SYMBOLIC_DELIBERATION_MAX_SUMMARY_BYTES = 2048;
|
|
36
|
+
const DEFAULT_COLLAB_ROLE_POLICY = 'enforce';
|
|
37
|
+
const COLLAB_ROLE_POLICY_ENV_CANONICAL = 'RLM_SYMBOLIC_MULTI_AGENT_ROLE_POLICY';
|
|
38
|
+
const COLLAB_ROLE_POLICY_ENV_LEGACY = 'RLM_COLLAB_ROLE_POLICY';
|
|
39
|
+
const COLLAB_ALLOW_DEFAULT_ROLE_ENV_CANONICAL = 'RLM_SYMBOLIC_MULTI_AGENT_ALLOW_DEFAULT_ROLE';
|
|
40
|
+
const COLLAB_ALLOW_DEFAULT_ROLE_ENV_LEGACY = 'RLM_COLLAB_ALLOW_DEFAULT_ROLE';
|
|
36
41
|
const UNBOUNDED_ITERATION_ALIASES = new Set(['unbounded', 'unlimited', 'infinite', 'infinity']);
|
|
42
|
+
const COLLAB_FEATURE_CANONICAL = 'multi_agent';
|
|
43
|
+
const COLLAB_FEATURE_LEGACY = 'collab';
|
|
44
|
+
const COLLAB_ROLE_TAG_PATTERN = /^\s*\[(?:agent_type|role)\s*:\s*([a-z0-9._-]+)\]/i;
|
|
45
|
+
const COLLAB_ROLE_TOKEN_PATTERN = /^[a-z0-9._-]+$/;
|
|
37
46
|
function parseArgs(argv) {
|
|
38
47
|
const parsed = {};
|
|
39
48
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -102,6 +111,15 @@ function envFlagEnabled(value) {
|
|
|
102
111
|
const normalized = value.trim().toLowerCase();
|
|
103
112
|
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
104
113
|
}
|
|
114
|
+
function resolveSymbolicMultiAgentConfig(env) {
|
|
115
|
+
if (env.RLM_SYMBOLIC_MULTI_AGENT !== undefined) {
|
|
116
|
+
return { enabled: envFlagEnabled(env.RLM_SYMBOLIC_MULTI_AGENT), source: 'canonical' };
|
|
117
|
+
}
|
|
118
|
+
if (env.RLM_SYMBOLIC_COLLAB !== undefined) {
|
|
119
|
+
return { enabled: envFlagEnabled(env.RLM_SYMBOLIC_COLLAB), source: 'legacy' };
|
|
120
|
+
}
|
|
121
|
+
return { enabled: false, source: null };
|
|
122
|
+
}
|
|
105
123
|
function shouldForceNonInteractive(env) {
|
|
106
124
|
const stdinIsTTY = process.stdin?.isTTY === true;
|
|
107
125
|
return (!stdinIsTTY ||
|
|
@@ -196,9 +214,9 @@ function resolveRlmMode(rawMode, options) {
|
|
|
196
214
|
if (normalized !== 'auto') {
|
|
197
215
|
return null;
|
|
198
216
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
217
|
+
const largeContext = options.contextBytes >= options.symbolicMinBytes;
|
|
218
|
+
const explicitContextSignal = options.hasContextPath || options.delegated;
|
|
219
|
+
if (largeContext && explicitContextSignal) {
|
|
202
220
|
return 'symbolic';
|
|
203
221
|
}
|
|
204
222
|
return 'iterative';
|
|
@@ -323,9 +341,19 @@ async function runCodexCompletion(prompt, env, repoRoot, nonInteractive, subagen
|
|
|
323
341
|
async function runCodexJsonlCompletion(prompt, env, repoRoot, nonInteractive, mirrorOutput, extraArgs = [], options = {}) {
|
|
324
342
|
const { stdout, stderr } = await runCodexExec(['exec', '--json', ...extraArgs, prompt], env, repoRoot, nonInteractive, false, mirrorOutput);
|
|
325
343
|
if (options.validateCollabLifecycle) {
|
|
326
|
-
const
|
|
344
|
+
const rolePolicy = options.collabRolePolicy ?? DEFAULT_COLLAB_ROLE_POLICY;
|
|
345
|
+
const validation = validateCollabLifecycle(stdout, {
|
|
346
|
+
requireSpawnRole: rolePolicy !== 'off',
|
|
347
|
+
allowDefaultRole: options.collabAllowDefaultRole ?? false
|
|
348
|
+
});
|
|
327
349
|
if (!validation.ok) {
|
|
328
|
-
|
|
350
|
+
const rolePolicyFailure = isRolePolicyValidationReason(validation.reasonCode);
|
|
351
|
+
if (rolePolicy === 'warn' && rolePolicyFailure) {
|
|
352
|
+
logger.warn(`Collab lifecycle validation warning: ${validation.reason}`);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
throw new Error(`Collab lifecycle validation failed: ${validation.reason}`);
|
|
356
|
+
}
|
|
329
357
|
}
|
|
330
358
|
}
|
|
331
359
|
const message = extractAgentMessageFromJsonl(stdout);
|
|
@@ -334,6 +362,50 @@ async function runCodexJsonlCompletion(prompt, env, repoRoot, nonInteractive, mi
|
|
|
334
362
|
}
|
|
335
363
|
return [stdout.trim(), stderr.trim()].filter(Boolean).join('\n');
|
|
336
364
|
}
|
|
365
|
+
function parseFeatureFlagsFromText(raw) {
|
|
366
|
+
const flags = {};
|
|
367
|
+
for (const line of raw.split(/\r?\n/u)) {
|
|
368
|
+
const trimmed = line.trim();
|
|
369
|
+
if (!trimmed) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const tokens = trimmed.split(/\s+/u);
|
|
373
|
+
if (tokens.length < 2) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const name = tokens[0] ?? '';
|
|
377
|
+
const enabledToken = tokens[tokens.length - 1] ?? '';
|
|
378
|
+
if (!name) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (enabledToken === 'true') {
|
|
382
|
+
flags[name] = true;
|
|
383
|
+
}
|
|
384
|
+
else if (enabledToken === 'false') {
|
|
385
|
+
flags[name] = false;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return flags;
|
|
389
|
+
}
|
|
390
|
+
function resolveCollabFeatureKeyFromFlags(flags) {
|
|
391
|
+
if (Object.prototype.hasOwnProperty.call(flags, COLLAB_FEATURE_CANONICAL)) {
|
|
392
|
+
return COLLAB_FEATURE_CANONICAL;
|
|
393
|
+
}
|
|
394
|
+
if (Object.prototype.hasOwnProperty.call(flags, COLLAB_FEATURE_LEGACY)) {
|
|
395
|
+
return COLLAB_FEATURE_LEGACY;
|
|
396
|
+
}
|
|
397
|
+
return COLLAB_FEATURE_LEGACY;
|
|
398
|
+
}
|
|
399
|
+
async function resolveCollabFeatureKey(env, repoRoot, nonInteractive) {
|
|
400
|
+
try {
|
|
401
|
+
const { stdout } = await runCodexExec(['features', 'list'], env, repoRoot, nonInteractive, false, false);
|
|
402
|
+
return resolveCollabFeatureKeyFromFlags(parseFeatureFlagsFromText(stdout));
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
logger.debug(`Unable to resolve Codex collab feature key via \`codex features list\`: ${error instanceof Error ? error.message : String(error)}`);
|
|
406
|
+
return COLLAB_FEATURE_LEGACY;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
337
409
|
function extractAgentMessageFromJsonl(raw) {
|
|
338
410
|
let lastMessage = null;
|
|
339
411
|
const lines = raw.split(/\r?\n/);
|
|
@@ -380,12 +452,16 @@ function parseCollabToolCallsFromJsonl(raw) {
|
|
|
380
452
|
const receiverThreadIds = Array.isArray(parsed.item.receiver_thread_ids)
|
|
381
453
|
? parsed.item.receiver_thread_ids.filter((entry) => typeof entry === 'string')
|
|
382
454
|
: [];
|
|
455
|
+
const prompt = typeof parsed.item.prompt === 'string' ? parsed.item.prompt : null;
|
|
383
456
|
calls.push({
|
|
384
457
|
sequence: index,
|
|
385
458
|
eventType: parsed.type,
|
|
386
459
|
tool: parsed.item.tool,
|
|
387
460
|
status: normalizeCollabStatus(parsed.item.status),
|
|
388
|
-
receiverThreadIds
|
|
461
|
+
receiverThreadIds,
|
|
462
|
+
prompt,
|
|
463
|
+
agentType: normalizeCollabRoleToken(parsed.item.agent_type),
|
|
464
|
+
promptRole: resolveCollabRoleFromPrompt(prompt)
|
|
389
465
|
});
|
|
390
466
|
}
|
|
391
467
|
catch {
|
|
@@ -397,6 +473,82 @@ function parseCollabToolCallsFromJsonl(raw) {
|
|
|
397
473
|
function formatLifecycleIds(ids) {
|
|
398
474
|
return ids.slice(0, 3).join(', ');
|
|
399
475
|
}
|
|
476
|
+
function normalizeCollabRoleToken(value) {
|
|
477
|
+
if (typeof value !== 'string') {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const normalized = value.trim().toLowerCase();
|
|
481
|
+
if (!normalized || !COLLAB_ROLE_TOKEN_PATTERN.test(normalized)) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
return normalized;
|
|
485
|
+
}
|
|
486
|
+
function resolveCollabRoleFromPrompt(value) {
|
|
487
|
+
if (typeof value !== 'string') {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const match = value.match(COLLAB_ROLE_TAG_PATTERN);
|
|
491
|
+
if (!match || typeof match[1] !== 'string') {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
return normalizeCollabRoleToken(match[1]);
|
|
495
|
+
}
|
|
496
|
+
function resolveCollabRolePolicy(value) {
|
|
497
|
+
const normalized = (value ?? '').trim().toLowerCase();
|
|
498
|
+
if (!normalized) {
|
|
499
|
+
return DEFAULT_COLLAB_ROLE_POLICY;
|
|
500
|
+
}
|
|
501
|
+
if (normalized === 'off' ||
|
|
502
|
+
normalized === 'disabled' ||
|
|
503
|
+
normalized === 'none' ||
|
|
504
|
+
normalized === '0' ||
|
|
505
|
+
normalized === 'false') {
|
|
506
|
+
return 'off';
|
|
507
|
+
}
|
|
508
|
+
if (normalized === 'warn' || normalized === 'warning' || normalized === 'soft') {
|
|
509
|
+
return 'warn';
|
|
510
|
+
}
|
|
511
|
+
if (normalized === 'enforce' || normalized === 'strict' || normalized === 'on' || normalized === 'true' || normalized === '1') {
|
|
512
|
+
return 'enforce';
|
|
513
|
+
}
|
|
514
|
+
logger.warn(`Invalid multi-agent role policy value "${value}". Using "${DEFAULT_COLLAB_ROLE_POLICY}" ` +
|
|
515
|
+
`(expected: enforce|warn|off; canonical env ${COLLAB_ROLE_POLICY_ENV_CANONICAL}, ` +
|
|
516
|
+
`legacy alias ${COLLAB_ROLE_POLICY_ENV_LEGACY}).`);
|
|
517
|
+
return DEFAULT_COLLAB_ROLE_POLICY;
|
|
518
|
+
}
|
|
519
|
+
function resolveSymbolicMultiAgentRolePolicyConfig(env) {
|
|
520
|
+
const canonical = env[COLLAB_ROLE_POLICY_ENV_CANONICAL];
|
|
521
|
+
const legacy = env[COLLAB_ROLE_POLICY_ENV_LEGACY];
|
|
522
|
+
if (canonical !== undefined) {
|
|
523
|
+
if (legacy !== undefined && legacy.trim().toLowerCase() !== canonical.trim().toLowerCase()) {
|
|
524
|
+
logger.warn(`${COLLAB_ROLE_POLICY_ENV_LEGACY} is ignored because ${COLLAB_ROLE_POLICY_ENV_CANONICAL} is set.`);
|
|
525
|
+
}
|
|
526
|
+
return { value: resolveCollabRolePolicy(canonical), source: 'canonical' };
|
|
527
|
+
}
|
|
528
|
+
if (legacy !== undefined) {
|
|
529
|
+
return { value: resolveCollabRolePolicy(legacy), source: 'legacy' };
|
|
530
|
+
}
|
|
531
|
+
return { value: resolveCollabRolePolicy(undefined), source: null };
|
|
532
|
+
}
|
|
533
|
+
function resolveSymbolicMultiAgentAllowDefaultRoleConfig(env) {
|
|
534
|
+
const canonical = env[COLLAB_ALLOW_DEFAULT_ROLE_ENV_CANONICAL];
|
|
535
|
+
const legacy = env[COLLAB_ALLOW_DEFAULT_ROLE_ENV_LEGACY];
|
|
536
|
+
if (canonical !== undefined) {
|
|
537
|
+
if (legacy !== undefined && envFlagEnabled(legacy) !== envFlagEnabled(canonical)) {
|
|
538
|
+
logger.warn(`${COLLAB_ALLOW_DEFAULT_ROLE_ENV_LEGACY} is ignored because ${COLLAB_ALLOW_DEFAULT_ROLE_ENV_CANONICAL} is set.`);
|
|
539
|
+
}
|
|
540
|
+
return { value: envFlagEnabled(canonical), source: 'canonical' };
|
|
541
|
+
}
|
|
542
|
+
if (legacy !== undefined) {
|
|
543
|
+
return { value: envFlagEnabled(legacy), source: 'legacy' };
|
|
544
|
+
}
|
|
545
|
+
return { value: false, source: null };
|
|
546
|
+
}
|
|
547
|
+
function isRolePolicyValidationReason(reasonCode) {
|
|
548
|
+
return (reasonCode === 'missing_role' ||
|
|
549
|
+
reasonCode === 'default_role_disallowed' ||
|
|
550
|
+
reasonCode === 'role_mismatch');
|
|
551
|
+
}
|
|
400
552
|
function includesThreadLimit(text) {
|
|
401
553
|
return text.toLowerCase().includes('agent thread limit reached');
|
|
402
554
|
}
|
|
@@ -438,9 +590,11 @@ function hasCollabSpawnThreadLimitError(raw) {
|
|
|
438
590
|
}
|
|
439
591
|
return false;
|
|
440
592
|
}
|
|
441
|
-
function validateCollabLifecycle(raw) {
|
|
593
|
+
function validateCollabLifecycle(raw, options = {}) {
|
|
594
|
+
const requireSpawnRole = options.requireSpawnRole !== false;
|
|
595
|
+
const allowDefaultRole = options.allowDefaultRole === true;
|
|
442
596
|
if (hasCollabSpawnThreadLimitError(raw)) {
|
|
443
|
-
return { ok: false, reason: 'collab spawn hit thread limit' };
|
|
597
|
+
return { ok: false, reason: 'collab spawn hit thread limit', reasonCode: 'thread_limit' };
|
|
444
598
|
}
|
|
445
599
|
const calls = parseCollabToolCallsFromJsonl(raw);
|
|
446
600
|
if (calls.length === 0) {
|
|
@@ -449,16 +603,53 @@ function validateCollabLifecycle(raw) {
|
|
|
449
603
|
const spawnedAt = new Map();
|
|
450
604
|
const waitedAt = new Map();
|
|
451
605
|
const closedAt = new Map();
|
|
606
|
+
const missingRoleIds = new Set();
|
|
607
|
+
const disallowedDefaultRoleIds = new Set();
|
|
608
|
+
const mismatchedRoleIds = new Set();
|
|
609
|
+
const roleByThread = new Map();
|
|
452
610
|
for (const call of calls) {
|
|
453
611
|
const isCompleted = call.status === 'completed' || (call.status === 'unknown' && call.eventType === 'item.completed');
|
|
454
612
|
if (!isCompleted) {
|
|
455
613
|
continue;
|
|
456
614
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
615
|
+
if (call.tool === 'spawn_agent') {
|
|
616
|
+
const explicitRole = call.agentType;
|
|
617
|
+
const promptRole = call.promptRole;
|
|
618
|
+
const effectiveRole = explicitRole ?? promptRole;
|
|
619
|
+
const roleTargets = call.receiverThreadIds.length > 0 ? call.receiverThreadIds : [`spawn@${call.sequence}`];
|
|
620
|
+
if (requireSpawnRole && !effectiveRole) {
|
|
621
|
+
for (const target of roleTargets) {
|
|
622
|
+
missingRoleIds.add(target);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
else if (effectiveRole === 'default' && !allowDefaultRole) {
|
|
626
|
+
for (const target of roleTargets) {
|
|
627
|
+
disallowedDefaultRoleIds.add(target);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (explicitRole && promptRole && explicitRole !== promptRole) {
|
|
631
|
+
for (const target of roleTargets) {
|
|
632
|
+
mismatchedRoleIds.add(target);
|
|
633
|
+
}
|
|
460
634
|
}
|
|
461
|
-
|
|
635
|
+
for (const threadId of call.receiverThreadIds) {
|
|
636
|
+
if (!spawnedAt.has(threadId)) {
|
|
637
|
+
spawnedAt.set(threadId, call.sequence);
|
|
638
|
+
}
|
|
639
|
+
if (!effectiveRole) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const previous = roleByThread.get(threadId);
|
|
643
|
+
if (previous && previous !== effectiveRole) {
|
|
644
|
+
mismatchedRoleIds.add(threadId);
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
roleByThread.set(threadId, effectiveRole);
|
|
648
|
+
}
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
for (const threadId of call.receiverThreadIds) {
|
|
652
|
+
if (call.tool === 'wait') {
|
|
462
653
|
waitedAt.set(threadId, call.sequence);
|
|
463
654
|
}
|
|
464
655
|
else if (call.tool === 'close_agent') {
|
|
@@ -467,35 +658,63 @@ function validateCollabLifecycle(raw) {
|
|
|
467
658
|
}
|
|
468
659
|
}
|
|
469
660
|
const spawnedIds = Array.from(spawnedAt.keys());
|
|
470
|
-
if (spawnedIds.length
|
|
661
|
+
if (spawnedIds.length > 0) {
|
|
662
|
+
const missingWait = spawnedIds.filter((threadId) => !waitedAt.has(threadId));
|
|
663
|
+
if (missingWait.length > 0) {
|
|
664
|
+
return {
|
|
665
|
+
ok: false,
|
|
666
|
+
reason: `missing wait for spawned agent(s): ${formatLifecycleIds(missingWait)}`,
|
|
667
|
+
reasonCode: 'missing_wait'
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
const missingClose = spawnedIds.filter((threadId) => !closedAt.has(threadId));
|
|
671
|
+
if (missingClose.length > 0) {
|
|
672
|
+
return {
|
|
673
|
+
ok: false,
|
|
674
|
+
reason: `missing close_agent for spawned agent(s): ${formatLifecycleIds(missingClose)}`,
|
|
675
|
+
reasonCode: 'missing_close'
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const invalidOrder = spawnedIds.filter((threadId) => {
|
|
679
|
+
const waitSequence = waitedAt.get(threadId);
|
|
680
|
+
const closeSequence = closedAt.get(threadId);
|
|
681
|
+
if (waitSequence === undefined || closeSequence === undefined) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
return closeSequence < waitSequence;
|
|
685
|
+
});
|
|
686
|
+
if (invalidOrder.length > 0) {
|
|
687
|
+
return {
|
|
688
|
+
ok: false,
|
|
689
|
+
reason: `close_agent before wait for agent(s): ${formatLifecycleIds(invalidOrder)}`,
|
|
690
|
+
reasonCode: 'close_before_wait'
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (!requireSpawnRole) {
|
|
471
695
|
return { ok: true };
|
|
472
696
|
}
|
|
473
|
-
|
|
474
|
-
if (missingWait.length > 0) {
|
|
697
|
+
if (missingRoleIds.size > 0) {
|
|
475
698
|
return {
|
|
476
699
|
ok: false,
|
|
477
|
-
reason: `missing
|
|
700
|
+
reason: `missing explicit role for spawn_agent call(s): ${formatLifecycleIds(Array.from(missingRoleIds))}. ` +
|
|
701
|
+
'Prefix prompts with [agent_type:<role>] and set spawn_agent.agent_type when supported.',
|
|
702
|
+
reasonCode: 'missing_role'
|
|
478
703
|
};
|
|
479
704
|
}
|
|
480
|
-
|
|
481
|
-
if (missingClose.length > 0) {
|
|
705
|
+
if (disallowedDefaultRoleIds.size > 0) {
|
|
482
706
|
return {
|
|
483
707
|
ok: false,
|
|
484
|
-
reason: `
|
|
708
|
+
reason: `spawn_agent used disallowed default role for: ${formatLifecycleIds(Array.from(disallowedDefaultRoleIds))}. ` +
|
|
709
|
+
'Set a non-default agent_type explicitly.',
|
|
710
|
+
reasonCode: 'default_role_disallowed'
|
|
485
711
|
};
|
|
486
712
|
}
|
|
487
|
-
|
|
488
|
-
const waitSequence = waitedAt.get(threadId);
|
|
489
|
-
const closeSequence = closedAt.get(threadId);
|
|
490
|
-
if (waitSequence === undefined || closeSequence === undefined) {
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
return closeSequence < waitSequence;
|
|
494
|
-
});
|
|
495
|
-
if (invalidOrder.length > 0) {
|
|
713
|
+
if (mismatchedRoleIds.size > 0) {
|
|
496
714
|
return {
|
|
497
715
|
ok: false,
|
|
498
|
-
reason: `
|
|
716
|
+
reason: `spawn_agent role mismatch for agent(s): ${formatLifecycleIds(Array.from(mismatchedRoleIds))}`,
|
|
717
|
+
reasonCode: 'role_mismatch'
|
|
499
718
|
};
|
|
500
719
|
}
|
|
501
720
|
return { ok: true };
|
|
@@ -504,7 +723,8 @@ function buildCollabSubcallPrompt(prompt) {
|
|
|
504
723
|
return [
|
|
505
724
|
'Use collab tools to run the sub-agent prompt below.',
|
|
506
725
|
'For every spawned agent id, execute this lifecycle in order:',
|
|
507
|
-
'1) spawn_agent',
|
|
726
|
+
'1) spawn_agent with explicit agent_type (never omit it; omission defaults to `default`),',
|
|
727
|
+
' and prefix the spawned prompt with [agent_type:<same-role>] on the first line',
|
|
508
728
|
'2) wait (for that same id)',
|
|
509
729
|
'3) close_agent (for that same id)',
|
|
510
730
|
'Never leave spawned agents unclosed, including timeout or error paths.',
|
|
@@ -772,7 +992,12 @@ async function main() {
|
|
|
772
992
|
logger.info(`Validator: ${validatorCommand}`);
|
|
773
993
|
}
|
|
774
994
|
const subagentsEnabled = envFlagEnabled(env.CODEX_SUBAGENTS) || envFlagEnabled(env.RLM_SUBAGENTS);
|
|
775
|
-
const
|
|
995
|
+
const symbolicMultiAgent = resolveSymbolicMultiAgentConfig(env);
|
|
996
|
+
const symbolicCollabEnabled = symbolicMultiAgent.enabled;
|
|
997
|
+
const collabRolePolicyConfig = resolveSymbolicMultiAgentRolePolicyConfig(env);
|
|
998
|
+
const collabRolePolicy = collabRolePolicyConfig.value;
|
|
999
|
+
const collabAllowDefaultRoleConfig = resolveSymbolicMultiAgentAllowDefaultRoleConfig(env);
|
|
1000
|
+
const collabAllowDefaultRole = collabAllowDefaultRoleConfig.value;
|
|
776
1001
|
const symbolicDeliberationEnabled = env.RLM_SYMBOLIC_DELIBERATION === undefined
|
|
777
1002
|
? true
|
|
778
1003
|
: envFlagEnabled(env.RLM_SYMBOLIC_DELIBERATION);
|
|
@@ -782,6 +1007,22 @@ async function main() {
|
|
|
782
1007
|
const symbolicDeliberationLogArtifacts = envFlagEnabled(env.RLM_SYMBOLIC_DELIBERATION_LOG);
|
|
783
1008
|
const nonInteractive = shouldForceNonInteractive(env);
|
|
784
1009
|
if (mode === 'symbolic') {
|
|
1010
|
+
if (symbolicMultiAgent.source === 'legacy') {
|
|
1011
|
+
logger.warn('RLM_SYMBOLIC_COLLAB is a legacy alias; prefer RLM_SYMBOLIC_MULTI_AGENT.');
|
|
1012
|
+
}
|
|
1013
|
+
if (collabRolePolicyConfig.source === 'legacy') {
|
|
1014
|
+
logger.warn(`${COLLAB_ROLE_POLICY_ENV_LEGACY} is a legacy alias; prefer ${COLLAB_ROLE_POLICY_ENV_CANONICAL}.`);
|
|
1015
|
+
}
|
|
1016
|
+
if (collabAllowDefaultRoleConfig.source === 'legacy') {
|
|
1017
|
+
logger.warn(`${COLLAB_ALLOW_DEFAULT_ROLE_ENV_LEGACY} is a legacy alias; prefer ${COLLAB_ALLOW_DEFAULT_ROLE_ENV_CANONICAL}.`);
|
|
1018
|
+
}
|
|
1019
|
+
const collabFeatureKey = symbolicCollabEnabled
|
|
1020
|
+
? await resolveCollabFeatureKey(env, repoRoot, nonInteractive)
|
|
1021
|
+
: COLLAB_FEATURE_LEGACY;
|
|
1022
|
+
if (symbolicCollabEnabled) {
|
|
1023
|
+
logger.info(`Symbolic collab feature key: ${collabFeatureKey}`);
|
|
1024
|
+
logger.info(`Symbolic collab role policy: ${collabRolePolicy} (allow_default_role=${collabAllowDefaultRole ? '1' : '0'})`);
|
|
1025
|
+
}
|
|
785
1026
|
const budgets = {
|
|
786
1027
|
maxSubcallsPerIteration: parsePositiveInt(env.RLM_MAX_SUBCALLS_PER_ITERATION, DEFAULT_MAX_SUBCALLS_PER_ITERATION) ??
|
|
787
1028
|
0,
|
|
@@ -881,11 +1122,13 @@ async function main() {
|
|
|
881
1122
|
const collabPrompt = buildCollabSubcallPrompt(prompt);
|
|
882
1123
|
return runCodexJsonlCompletion(collabPrompt, env, repoRoot, nonInteractive, true, [
|
|
883
1124
|
'--enable',
|
|
884
|
-
|
|
1125
|
+
collabFeatureKey,
|
|
885
1126
|
'--sandbox',
|
|
886
1127
|
'read-only'
|
|
887
1128
|
], {
|
|
888
|
-
validateCollabLifecycle: true
|
|
1129
|
+
validateCollabLifecycle: true,
|
|
1130
|
+
collabRolePolicy,
|
|
1131
|
+
collabAllowDefaultRole
|
|
889
1132
|
});
|
|
890
1133
|
},
|
|
891
1134
|
deliberation: {
|
|
@@ -904,11 +1147,13 @@ async function main() {
|
|
|
904
1147
|
const collabPrompt = buildCollabSubcallPrompt(prompt);
|
|
905
1148
|
return runCodexJsonlCompletion(collabPrompt, env, repoRoot, nonInteractive, true, [
|
|
906
1149
|
'--enable',
|
|
907
|
-
|
|
1150
|
+
collabFeatureKey,
|
|
908
1151
|
'--sandbox',
|
|
909
1152
|
'read-only'
|
|
910
1153
|
], {
|
|
911
|
-
validateCollabLifecycle: true
|
|
1154
|
+
validateCollabLifecycle: true,
|
|
1155
|
+
collabRolePolicy,
|
|
1156
|
+
collabAllowDefaultRole
|
|
912
1157
|
});
|
|
913
1158
|
}
|
|
914
1159
|
},
|
|
@@ -956,10 +1201,19 @@ if (entry && entry === self) {
|
|
|
956
1201
|
export const __test__ = {
|
|
957
1202
|
parseMaxIterations,
|
|
958
1203
|
parsePositiveInt,
|
|
1204
|
+
resolveSymbolicMultiAgentConfig,
|
|
1205
|
+
resolveSymbolicMultiAgentRolePolicyConfig,
|
|
1206
|
+
resolveSymbolicMultiAgentAllowDefaultRoleConfig,
|
|
1207
|
+
parseFeatureFlagsFromText,
|
|
1208
|
+
resolveCollabFeatureKeyFromFlags,
|
|
1209
|
+
resolveCollabRolePolicy,
|
|
1210
|
+
isRolePolicyValidationReason,
|
|
959
1211
|
resolveRlmMode,
|
|
960
1212
|
parseCollabToolCallsFromJsonl,
|
|
961
1213
|
validateCollabLifecycle,
|
|
962
1214
|
buildCollabSubcallPrompt,
|
|
1215
|
+
COLLAB_FEATURE_CANONICAL,
|
|
1216
|
+
COLLAB_FEATURE_LEGACY,
|
|
963
1217
|
DEFAULT_MAX_ITERATIONS,
|
|
964
1218
|
DEFAULT_MAX_MINUTES,
|
|
965
1219
|
DEFAULT_SYMBOLIC_MIN_BYTES
|
|
@@ -58,6 +58,7 @@ export async function bootstrapManifest(runId, options) {
|
|
|
58
58
|
prompt_packs: [],
|
|
59
59
|
guardrails_required: pipeline.guardrailsRequired !== false,
|
|
60
60
|
cloud_execution: null,
|
|
61
|
+
cloud_fallback: null,
|
|
61
62
|
learning: {
|
|
62
63
|
validation: {
|
|
63
64
|
mode: 'per-task',
|
|
@@ -192,6 +193,7 @@ export function resetForResume(manifest) {
|
|
|
192
193
|
manifest.status_detail = 'resuming';
|
|
193
194
|
manifest.guardrail_status = undefined;
|
|
194
195
|
manifest.cloud_execution = null;
|
|
196
|
+
manifest.cloud_fallback = null;
|
|
195
197
|
}
|
|
196
198
|
export function recordResumeEvent(manifest, event) {
|
|
197
199
|
manifest.resume_events.push({ ...event, timestamp: isoTimestamp() });
|
|
@@ -228,18 +230,20 @@ function computeGuardrailStatus(manifest) {
|
|
|
228
230
|
other: 0
|
|
229
231
|
};
|
|
230
232
|
for (const entry of guardrailCommands) {
|
|
231
|
-
|
|
233
|
+
const status = classifyGuardrailCommand(entry);
|
|
234
|
+
if (status === 'succeeded') {
|
|
232
235
|
counts.succeeded += 1;
|
|
236
|
+
continue;
|
|
233
237
|
}
|
|
234
|
-
|
|
238
|
+
if (status === 'failed') {
|
|
235
239
|
counts.failed += 1;
|
|
240
|
+
continue;
|
|
236
241
|
}
|
|
237
|
-
|
|
242
|
+
if (status === 'skipped') {
|
|
238
243
|
counts.skipped += 1;
|
|
244
|
+
continue;
|
|
239
245
|
}
|
|
240
|
-
|
|
241
|
-
counts.other += 1;
|
|
242
|
-
}
|
|
246
|
+
counts.other += 1;
|
|
243
247
|
}
|
|
244
248
|
const present = counts.succeeded > 0;
|
|
245
249
|
let recommendation = null;
|
|
@@ -270,6 +274,27 @@ function selectGuardrailCommands(manifest) {
|
|
|
270
274
|
return haystack.includes('spec-guard') || haystack.includes('specguardrunner');
|
|
271
275
|
});
|
|
272
276
|
}
|
|
277
|
+
function classifyGuardrailCommand(entry) {
|
|
278
|
+
if (entry.status === 'failed') {
|
|
279
|
+
return 'failed';
|
|
280
|
+
}
|
|
281
|
+
if (entry.status === 'skipped') {
|
|
282
|
+
return 'skipped';
|
|
283
|
+
}
|
|
284
|
+
if (entry.status === 'succeeded') {
|
|
285
|
+
return isExplicitGuardrailSkip(entry.summary) ? 'skipped' : 'succeeded';
|
|
286
|
+
}
|
|
287
|
+
return 'other';
|
|
288
|
+
}
|
|
289
|
+
function isExplicitGuardrailSkip(summary) {
|
|
290
|
+
const normalized = summary?.toLowerCase() ?? '';
|
|
291
|
+
if (!normalized) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
return (normalized.includes('[spec-guard] skipped') ||
|
|
295
|
+
normalized.includes('spec-guard skipped') ||
|
|
296
|
+
normalized.includes('spec guard skipped'));
|
|
297
|
+
}
|
|
273
298
|
function formatGuardrailSummary(counts) {
|
|
274
299
|
if (counts.total === 0) {
|
|
275
300
|
return 'Guardrails: spec-guard command not found.';
|
|
@@ -53,11 +53,19 @@ export async function runCommandStage(context, hooks = {}) {
|
|
|
53
53
|
let stderrTruncated = false;
|
|
54
54
|
let collabBuffer = '';
|
|
55
55
|
let collabCount = manifest.collab_tool_calls?.length ?? 0;
|
|
56
|
+
const manifestCaptureLimit = typeof manifest.collab_tool_calls_max_events === 'number'
|
|
57
|
+
? Math.max(0, Math.trunc(manifest.collab_tool_calls_max_events))
|
|
58
|
+
: null;
|
|
59
|
+
const hasLegacyUnknownCaptureHistory = manifestCaptureLimit === null && collabCount > 0;
|
|
60
|
+
const runCollabCaptureLimit = manifestCaptureLimit ?? Math.max(0, MAX_COLLAB_TOOL_CALLS);
|
|
61
|
+
if (!hasLegacyUnknownCaptureHistory) {
|
|
62
|
+
manifest.collab_tool_calls_max_events = runCollabCaptureLimit;
|
|
63
|
+
}
|
|
56
64
|
const recordCollabToolCall = (record) => {
|
|
57
|
-
if (
|
|
65
|
+
if (runCollabCaptureLimit <= 0) {
|
|
58
66
|
return;
|
|
59
67
|
}
|
|
60
|
-
if (collabCount >=
|
|
68
|
+
if (collabCount >= runCollabCaptureLimit) {
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
63
71
|
if (!manifest.collab_tool_calls) {
|