feat: add per-group workDir for agent working directory override

Agents can now run in a specified project directory instead of the group
folder. The group's CLAUDE.md is still loaded via additionalDirectories.
This commit is contained in:
Eyejoker
2026-03-11 02:02:51 +09:00
parent 82becfdfd7
commit cb51ef36f9
6 changed files with 39 additions and 11 deletions

View File

@@ -60,6 +60,8 @@ const GROUP_DIR = process.env.NANOCLAW_GROUP_DIR || '/workspace/group';
const IPC_DIR = process.env.NANOCLAW_IPC_DIR || '/workspace/ipc';
const GLOBAL_DIR = process.env.NANOCLAW_GLOBAL_DIR || '/workspace/global';
const EXTRA_BASE = process.env.NANOCLAW_EXTRA_DIR || '/workspace/extra';
// Optional: override cwd (agent works in this directory instead of GROUP_DIR)
const WORK_DIR = process.env.NANOCLAW_WORK_DIR || '';
const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
const MAX_TURNS = 100;
@@ -417,6 +419,12 @@ async function runQuery(
}
}
}
// When WORK_DIR is set, use it as cwd and include GROUP_DIR as additional directory
const effectiveCwd = WORK_DIR || GROUP_DIR;
if (WORK_DIR && WORK_DIR !== GROUP_DIR) {
extraDirs.push(GROUP_DIR);
log(`Work directory override: ${WORK_DIR} (group dir added to additionalDirectories)`);
}
if (extraDirs.length > 0) {
log(`Additional directories: ${extraDirs.join(', ')}`);
}
@@ -440,7 +448,7 @@ async function runQuery(
for await (const message of query({
prompt: stream,
options: {
cwd: GROUP_DIR,
cwd: effectiveCwd,
model,
thinking,
effort,
@@ -550,6 +558,9 @@ async function main(): Promise<void> {
// Clean up stale _close sentinel from previous container runs
try { fs.unlinkSync(IPC_INPUT_CLOSE_SENTINEL); } catch { /* ignore */ }
// Effective working directory (WORK_DIR overrides GROUP_DIR)
const mainEffectiveCwd = WORK_DIR || GROUP_DIR;
// Build initial prompt (drain any pending IPC messages too)
let prompt = containerInput.prompt;
if (containerInput.isScheduledTask) {
@@ -579,7 +590,7 @@ async function main(): Promise<void> {
for await (const message of query({
prompt: trimmedPrompt,
options: {
cwd: GROUP_DIR,
cwd: mainEffectiveCwd,
resume: sessionId,
systemPrompt: undefined,
allowedTools: [],

View File

@@ -36,6 +36,8 @@ interface ContainerOutput {
// Paths configurable via env vars (defaults to container paths for backwards compat)
const GROUP_DIR = process.env.NANOCLAW_GROUP_DIR || '/workspace/group';
const IPC_DIR = process.env.NANOCLAW_IPC_DIR || '/workspace/ipc';
// Optional: override cwd (agent works in this directory instead of GROUP_DIR)
const WORK_DIR = process.env.NANOCLAW_WORK_DIR || '';
const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
const IPC_INPUT_CLOSE_SENTINEL = path.join(IPC_INPUT_DIR, '_close');
const IPC_POLL_MS = 500;
@@ -125,6 +127,7 @@ function waitForIpcMessage(): Promise<string | null> {
async function runCodexExec(prompt: string, resume: boolean): Promise<{ result: string; error?: string }> {
return new Promise((resolve) => {
const args: string[] = [];
const effectiveCwd = WORK_DIR || GROUP_DIR;
if (resume) {
// Resume the most recent session with a new prompt
@@ -143,7 +146,7 @@ async function runCodexExec(prompt: string, resume: boolean): Promise<{ result:
args.push(
'exec',
'--dangerously-bypass-approvals-and-sandbox',
'-C', GROUP_DIR,
'-C', effectiveCwd,
'--skip-git-repo-check',
'--color', 'never',
prompt,
@@ -154,7 +157,7 @@ async function runCodexExec(prompt: string, resume: boolean): Promise<{ result:
const codex: ChildProcess = spawn('codex', args, {
stdio: ['pipe', 'pipe', 'pipe'],
cwd: GROUP_DIR,
cwd: effectiveCwd,
env: { ...process.env },
});