feat(codex): support proactive /agent mode (#1172)

This commit is contained in:
SSU-WEI HUANG
2026-07-26 15:01:53 +08:00
committed by GitHub
parent 6bddc9d044
commit bd5e87898a
7 changed files with 78 additions and 4 deletions
+1
View File
@@ -15,6 +15,7 @@ export interface EnhancedMode {
permissionMode: PermissionMode;
model?: string;
collaborationMode: CodexCollaborationMode;
proactiveMultiAgent?: boolean;
modelReasoningEffort?: ReasoningEffort;
/**
* Service tier override. `undefined` leaves it untouched (account default),
+10 -1
View File
@@ -73,6 +73,7 @@ export async function runCodex(opts: {
model: mode.model,
modelReasoningEffort: mode.modelReasoningEffort,
collaborationMode: mode.collaborationMode,
proactiveMultiAgent: mode.proactiveMultiAgent,
serviceTier: mode.serviceTier
}));
@@ -89,6 +90,7 @@ export async function runCodex(opts: {
let currentModel = opts.model;
let currentModelReasoningEffort: ReasoningEffort | undefined = opts.modelReasoningEffort;
let currentCollaborationMode: EnhancedMode['collaborationMode'] = opts.collaborationMode ?? 'default';
let currentProactiveMultiAgent: boolean | undefined;
// Service tier (Fast mode), stored representation: `'fast'` and
// `'standard'` are explicit user choices, `undefined`/`null` mean untouched
// (use the account default). Prefer the spawn-time override (set by the hub
@@ -137,6 +139,7 @@ export async function runCodex(opts: {
modelReasoningEffort?: ReasoningEffort | null;
collaborationMode?: EnhancedMode['collaborationMode'];
serviceTier?: string | null;
proactiveMultiAgent?: boolean;
} | undefined): void => {
if (!updates) return;
if (updates.permissionMode !== undefined) {
@@ -154,6 +157,9 @@ export async function runCodex(opts: {
if (updates.serviceTier !== undefined) {
currentServiceTier = updates.serviceTier;
}
if (updates.proactiveMultiAgent !== undefined) {
currentProactiveMultiAgent = updates.proactiveMultiAgent;
}
applyCurrentConfigToSession();
};
@@ -194,7 +200,8 @@ export async function runCodex(opts: {
collaborationMode: currentCollaborationMode,
model: currentModel,
modelReasoningEffort: currentModelReasoningEffort,
serviceTier: currentServiceTier
serviceTier: currentServiceTier,
proactiveMultiAgent: currentProactiveMultiAgent
});
if (slash.kind === 'goal') {
if (slash.message) {
@@ -253,6 +260,7 @@ export async function runCodex(opts: {
model: currentModel,
modelReasoningEffort: currentModelReasoningEffort,
collaborationMode: currentCollaborationMode,
proactiveMultiAgent: currentProactiveMultiAgent,
serviceTier: currentServiceTier
};
if (isolatedCommandText) {
@@ -267,6 +275,7 @@ export async function runCodex(opts: {
model: currentModel,
modelReasoningEffort: currentModelReasoningEffort,
collaborationMode: currentCollaborationMode,
proactiveMultiAgent: currentProactiveMultiAgent,
serviceTier: currentServiceTier
};
messageQueue.push(formatMessageWithAttachments(message.content.text, message.content.attachments), enhancedMode, localId);
@@ -422,6 +422,23 @@ describe('appServerConfig', () => {
expect(instructions).toContain('Do not rely on parent turn reasoning settings for spawned agents');
});
it('injects proactive multi-agent instructions when /agent mode is enabled', () => {
const params = buildTurnStartParams({
threadId: 'thread-1',
message: 'work',
cwd: '/repo',
mode: {
permissionMode: 'default',
model: 'o3',
collaborationMode: 'default',
proactiveMultiAgent: true
}
});
expect(params.collaborationMode?.settings.developer_instructions)
.toContain('Proactive multi-agent delegation is active.');
});
it('rejects collaboration mode payloads without a resolved model', () => {
expect(() => buildTurnStartParams({
threadId: 'thread-1',
+9 -3
View File
@@ -124,8 +124,14 @@ function resolveInstructions(args: {
};
}
function appendCollaborationInstructions(developerInstructions: string): string {
return `${developerInstructions}\n\n${codexCollaborationSpawnAgentInstructions}`;
function appendCollaborationInstructions(developerInstructions: string, proactiveMultiAgent?: boolean): string {
if (proactiveMultiAgent === undefined) {
return `${developerInstructions}\n\n${codexCollaborationSpawnAgentInstructions}`;
}
const multiAgentMode = proactiveMultiAgent
? 'Proactive multi-agent delegation is active. Any earlier instruction requiring an explicit user request before spawning sub-agents no longer applies. Use sub-agents when parallel work would materially improve speed or quality. This mode remains active until a later multi-agent mode developer message changes it.'
: 'Any earlier instruction enabling proactive multi-agent delegation no longer applies. Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work.';
return `${developerInstructions}\n\n${codexCollaborationSpawnAgentInstructions}\n\n<multi_agent_mode>${multiAgentMode}</multi_agent_mode>`;
}
function mentionNameFromPath(path: string): string {
@@ -275,7 +281,7 @@ export function buildTurnStartParams(args: {
settings: {
model,
reasoning_effort: modelReasoningEffort ?? null,
developer_instructions: appendCollaborationInstructions(developerInstructions)
developer_instructions: appendCollaborationInstructions(developerInstructions, args.mode?.proactiveMultiAgent)
}
};
} else if (model) {
+13
View File
@@ -9,6 +9,19 @@ const state = {
};
describe('resolveCodexSlashCommand', () => {
it('toggles proactive multi-agent mode', () => {
expect(resolveCodexSlashCommand('/agent', state)).toMatchObject({
updates: { proactiveMultiAgent: true }
});
expect(resolveCodexSlashCommand('/agent off', { ...state, proactiveMultiAgent: true })).toMatchObject({
updates: { proactiveMultiAgent: false }
});
expect(resolveCodexSlashCommand('/agent status', { ...state, proactiveMultiAgent: true })).toEqual({
kind: 'handled',
message: 'Codex proactive multi-agent mode: on'
});
});
it('enables plan mode without sending a turn', () => {
expect(resolveCodexSlashCommand('/plan', state)).toEqual({
kind: 'handled',
+27
View File
@@ -33,6 +33,7 @@ export type CodexSlashResolution =
model?: string | null;
modelReasoningEffort?: ReasoningEffort | null;
serviceTier?: string | null;
proactiveMultiAgent?: boolean;
};
}
| {
@@ -45,6 +46,7 @@ export type CodexSlashResolution =
model?: string | null;
modelReasoningEffort?: ReasoningEffort | null;
serviceTier?: string | null;
proactiveMultiAgent?: boolean;
};
}
| {
@@ -63,6 +65,7 @@ export function resolveCodexSlashCommand(
model?: string;
modelReasoningEffort?: ReasoningEffort;
serviceTier?: string | null;
proactiveMultiAgent?: boolean;
}
): CodexSlashResolution {
const match = /^\s*\/([a-z0-9:_-]+)(?:\s+([\s\S]*))?$/i.exec(text);
@@ -107,6 +110,30 @@ export function resolveCodexSlashCommand(
};
}
if (command === 'agent') {
const value = rest.toLowerCase();
if (value === 'status') {
return {
kind: 'handled',
message: `Codex proactive multi-agent mode: ${state.proactiveMultiAgent ? 'on' : 'off'}`
};
}
if (value && !['on', 'enable', 'enabled', 'off', 'disable', 'disabled'].includes(value)) {
return {
kind: 'handled',
message: 'Usage: /agent [on|off|status]'
};
}
const enabled = value
? ['on', 'enable', 'enabled'].includes(value)
: !state.proactiveMultiAgent;
return {
kind: 'handled',
message: `Codex proactive multi-agent mode ${enabled ? 'enabled' : 'disabled'}`,
updates: { proactiveMultiAgent: enabled }
};
}
if (command === 'goal') {
const lowerRest = rest.toLowerCase();
if (!rest) {