Merge origin/main into codex/local-memory-v1
This commit is contained in:
@@ -102,7 +102,8 @@ export type AgentTriggerReason =
|
||||
| 'session-failure'
|
||||
| 'overloaded'
|
||||
| 'network-error'
|
||||
| 'success-null-result';
|
||||
| 'success-null-result'
|
||||
| 'session-failure';
|
||||
|
||||
export type ClaudeRotationReason = Extract<
|
||||
AgentTriggerReason,
|
||||
|
||||
@@ -97,6 +97,26 @@ export async function runAgentProcess(
|
||||
memoryBriefing: input.memoryBriefing,
|
||||
role: containerRole,
|
||||
});
|
||||
// For codex: also write AGENTS.md to the reviewer session dir, because
|
||||
// the container's /home/node/.claude always mounts the reviewer session.
|
||||
// Arbiter and reviewer never run simultaneously, so this is safe.
|
||||
if (containerRole === 'arbiter') {
|
||||
const reviewerSessionDir = path.join(
|
||||
path.dirname(sessionDir),
|
||||
`${group.folder}-reviewer`,
|
||||
);
|
||||
const reviewerCodexDir = path.join(reviewerSessionDir, '.codex');
|
||||
if (fs.existsSync(reviewerSessionDir)) {
|
||||
fs.mkdirSync(reviewerCodexDir, { recursive: true });
|
||||
const arbiterAgentsMd = path.join(sessionDir, '.codex', 'AGENTS.md');
|
||||
if (fs.existsSync(arbiterAgentsMd)) {
|
||||
fs.copyFileSync(
|
||||
arbiterAgentsMd,
|
||||
path.join(reviewerCodexDir, 'AGENTS.md'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return runReviewerContainer({
|
||||
|
||||
@@ -321,6 +321,28 @@ export function buildReviewerMounts(
|
||||
readonly: false,
|
||||
});
|
||||
|
||||
// Owner session directory: read-only so reviewer can verify runtime state
|
||||
// files (cron state, configs, etc.) that the owner references by absolute path.
|
||||
const ownerSessionDir = path.join(DATA_DIR, 'sessions', group.folder);
|
||||
if (fs.existsSync(ownerSessionDir)) {
|
||||
mounts.push({
|
||||
hostPath: ownerSessionDir,
|
||||
containerPath: ownerSessionDir,
|
||||
readonly: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Codex OAuth: mount host's ~/.codex (read-only) so codex-runner can authenticate
|
||||
const hostCodexHome =
|
||||
process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
||||
if (fs.existsSync(hostCodexHome)) {
|
||||
mounts.push({
|
||||
hostPath: hostCodexHome,
|
||||
containerPath: '/home/node/.codex',
|
||||
readonly: true,
|
||||
});
|
||||
}
|
||||
|
||||
return mounts;
|
||||
}
|
||||
|
||||
@@ -367,12 +389,20 @@ function buildCreateArgs(
|
||||
args.push('-e', `SENTRY_AUTH_TOKEN=${process.env.SENTRY_AUTH_TOKEN}`);
|
||||
}
|
||||
|
||||
// Extra env overrides from paired-execution-context
|
||||
// Extra env overrides from paired-execution-context.
|
||||
// Model/effort keys are excluded here — they are injected per-exec so the
|
||||
// correct agent-type-specific values are used (claude vs codex).
|
||||
const perExecKeys = new Set([
|
||||
'CLAUDE_MODEL',
|
||||
'CLAUDE_EFFORT',
|
||||
'CODEX_MODEL',
|
||||
'CODEX_EFFORT',
|
||||
]);
|
||||
if (envOverrides) {
|
||||
for (const [key, value] of Object.entries(envOverrides)) {
|
||||
// Already set above — skip duplicates
|
||||
if (key === 'ANTHROPIC_API_KEY' || key === 'CLAUDE_CODE_OAUTH_TOKEN')
|
||||
continue;
|
||||
if (perExecKeys.has(key)) continue;
|
||||
if (key === 'EJCLAW_WORK_DIR') {
|
||||
args.push('-e', 'EJCLAW_WORK_DIR=/workspace/project');
|
||||
continue;
|
||||
@@ -469,22 +499,38 @@ export async function runReviewerContainer(args: {
|
||||
'';
|
||||
execArgs.push('-e', `CLAUDE_CODE_OAUTH_TOKEN=${oauthToken}`);
|
||||
}
|
||||
// Inject model/effort overrides at exec-time so per-role config
|
||||
// takes effect even with persistent containers.
|
||||
const modelEnvKeys = [
|
||||
'CLAUDE_MODEL',
|
||||
'CLAUDE_EFFORT',
|
||||
'CODEX_MODEL',
|
||||
'CODEX_EFFORT',
|
||||
];
|
||||
const isCodexAgent = (group.agentType || 'claude-code') === 'codex';
|
||||
logger.info(
|
||||
{
|
||||
containerName,
|
||||
groupAgentType: group.agentType,
|
||||
isCodexAgent,
|
||||
runnerPath: isCodexAgent
|
||||
? '/app/codex/dist/index.js'
|
||||
: '/app/agent/dist/index.js',
|
||||
},
|
||||
'Container exec runner selection',
|
||||
);
|
||||
// Inject only agent-type-appropriate model/effort overrides per exec.
|
||||
if (envOverrides) {
|
||||
const modelEnvKeys = isCodexAgent
|
||||
? ['CODEX_MODEL', 'CODEX_EFFORT']
|
||||
: ['CLAUDE_MODEL', 'CLAUDE_EFFORT'];
|
||||
for (const key of modelEnvKeys) {
|
||||
if (envOverrides[key]) {
|
||||
execArgs.push('-e', `${key}=${envOverrides[key]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
execArgs.push(containerName, 'bun', '/app/dist/index.js');
|
||||
if (isCodexAgent) {
|
||||
// Use session-local .codex dir (contains AGENTS.md with role prompts)
|
||||
// instead of the host-mounted ~/.codex (which has owner-only config).
|
||||
execArgs.push('-e', 'CODEX_HOME=/home/node/.claude/.codex');
|
||||
}
|
||||
const runnerPath = isCodexAgent
|
||||
? '/app/codex/dist/index.js'
|
||||
: '/app/agent/dist/index.js';
|
||||
execArgs.push(containerName, 'bun', runnerPath);
|
||||
|
||||
return new Promise<AgentOutput>((resolve) => {
|
||||
const proc = spawn(CONTAINER_RUNTIME_BIN, execArgs, {
|
||||
|
||||
@@ -591,6 +591,7 @@ function buildModelConfigSection(): string {
|
||||
},
|
||||
];
|
||||
|
||||
const failover = getGlobalFailoverInfo();
|
||||
const lines = ['🤖 *모델 구성*'];
|
||||
for (const role of roleConfigs) {
|
||||
if (!role.agentType && role.label === 'Arbiter') continue;
|
||||
@@ -600,7 +601,15 @@ function buildModelConfigSection(): string {
|
||||
? process.env.CODEX_MODEL || 'codex'
|
||||
: process.env.CLAUDE_MODEL || 'claude';
|
||||
const model = role.model || defaultModel;
|
||||
lines.push(` **${role.label}** — ${type} \`${model}\``);
|
||||
|
||||
// Show fallback status for claude-code roles when global failover is active
|
||||
const isFallback = failover.active && type === 'claude-code';
|
||||
if (isFallback) {
|
||||
const fallbackModel = process.env.CODEX_MODEL || 'codex';
|
||||
lines.push(` **${role.label}** — codex \`${fallbackModel}\` (fallback)`);
|
||||
} else {
|
||||
lines.push(` **${role.label}** — ${type} \`${model}\``);
|
||||
}
|
||||
}
|
||||
|
||||
// MoA status
|
||||
@@ -612,8 +621,6 @@ function buildModelConfigSection(): string {
|
||||
lines.push(` **MoA** — ${refs}`);
|
||||
}
|
||||
|
||||
// Global failover
|
||||
const failover = getGlobalFailoverInfo();
|
||||
if (failover.active) {
|
||||
lines.push(` ⚠️ **Failover 활성** — ${failover.reason || '알 수 없음'}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user