chore: set printWidth to 120 and reformat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,12 +18,7 @@ import {
|
||||
} from './config.js';
|
||||
import { resolveGroupFolderPath, resolveGroupIpcPath } from './group-folder.js';
|
||||
import { logger } from './logger.js';
|
||||
import {
|
||||
CONTAINER_RUNTIME_BIN,
|
||||
hostGatewayArgs,
|
||||
readonlyMountArgs,
|
||||
stopContainer,
|
||||
} from './container-runtime.js';
|
||||
import { CONTAINER_RUNTIME_BIN, hostGatewayArgs, readonlyMountArgs, stopContainer } from './container-runtime.js';
|
||||
import { OneCLI } from '@onecli-sh/sdk';
|
||||
import { validateAdditionalMounts } from './mount-security.js';
|
||||
import { RegisteredGroup } from './types.js';
|
||||
@@ -58,10 +53,7 @@ interface VolumeMount {
|
||||
readonly: boolean;
|
||||
}
|
||||
|
||||
function buildVolumeMounts(
|
||||
group: RegisteredGroup,
|
||||
isMain: boolean,
|
||||
): VolumeMount[] {
|
||||
function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount[] {
|
||||
const mounts: VolumeMount[] = [];
|
||||
const projectRoot = process.cwd();
|
||||
const groupDir = resolveGroupFolderPath(group.folder);
|
||||
@@ -136,12 +128,7 @@ function buildVolumeMounts(
|
||||
|
||||
// Per-group Claude sessions directory (isolated from other groups)
|
||||
// Each group gets their own .claude/ to prevent cross-group session access
|
||||
const groupSessionsDir = path.join(
|
||||
DATA_DIR,
|
||||
'sessions',
|
||||
group.folder,
|
||||
'.claude',
|
||||
);
|
||||
const groupSessionsDir = path.join(DATA_DIR, 'sessions', group.folder, '.claude');
|
||||
fs.mkdirSync(groupSessionsDir, { recursive: true });
|
||||
const settingsFile = path.join(groupSessionsDir, 'settings.json');
|
||||
if (!fs.existsSync(settingsFile)) {
|
||||
@@ -199,26 +186,15 @@ function buildVolumeMounts(
|
||||
// Copy agent-runner source into a per-group writable location so agents
|
||||
// can customize it (add tools, change behavior) without affecting other
|
||||
// groups. Recompiled on container startup via entrypoint.sh.
|
||||
const agentRunnerSrc = path.join(
|
||||
projectRoot,
|
||||
'container',
|
||||
'agent-runner',
|
||||
'src',
|
||||
);
|
||||
const groupAgentRunnerDir = path.join(
|
||||
DATA_DIR,
|
||||
'sessions',
|
||||
group.folder,
|
||||
'agent-runner-src',
|
||||
);
|
||||
const agentRunnerSrc = path.join(projectRoot, 'container', 'agent-runner', 'src');
|
||||
const groupAgentRunnerDir = path.join(DATA_DIR, 'sessions', group.folder, 'agent-runner-src');
|
||||
if (fs.existsSync(agentRunnerSrc)) {
|
||||
const srcIndex = path.join(agentRunnerSrc, 'index.ts');
|
||||
const cachedIndex = path.join(groupAgentRunnerDir, 'index.ts');
|
||||
const needsCopy =
|
||||
!fs.existsSync(groupAgentRunnerDir) ||
|
||||
!fs.existsSync(cachedIndex) ||
|
||||
(fs.existsSync(srcIndex) &&
|
||||
fs.statSync(srcIndex).mtimeMs > fs.statSync(cachedIndex).mtimeMs);
|
||||
(fs.existsSync(srcIndex) && fs.statSync(srcIndex).mtimeMs > fs.statSync(cachedIndex).mtimeMs);
|
||||
if (needsCopy) {
|
||||
fs.cpSync(agentRunnerSrc, groupAgentRunnerDir, { recursive: true });
|
||||
}
|
||||
@@ -231,11 +207,7 @@ function buildVolumeMounts(
|
||||
|
||||
// Additional mounts validated against external allowlist (tamper-proof from containers)
|
||||
if (group.containerConfig?.additionalMounts) {
|
||||
const validatedMounts = validateAdditionalMounts(
|
||||
group.containerConfig.additionalMounts,
|
||||
group.name,
|
||||
isMain,
|
||||
);
|
||||
const validatedMounts = validateAdditionalMounts(group.containerConfig.additionalMounts, group.name, isMain);
|
||||
mounts.push(...validatedMounts);
|
||||
}
|
||||
|
||||
@@ -261,10 +233,7 @@ async function buildContainerArgs(
|
||||
if (onecliApplied) {
|
||||
logger.info({ containerName }, 'OneCLI gateway config applied');
|
||||
} else {
|
||||
logger.warn(
|
||||
{ containerName },
|
||||
'OneCLI gateway not reachable — container will have no credentials',
|
||||
);
|
||||
logger.warn({ containerName }, 'OneCLI gateway not reachable — container will have no credentials');
|
||||
}
|
||||
|
||||
// Runtime-specific args for host gateway resolution
|
||||
@@ -308,23 +277,14 @@ export async function runContainerAgent(
|
||||
const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
const containerName = `nanoclaw-${safeName}-${Date.now()}`;
|
||||
// Main group uses the default OneCLI agent; others use their own agent.
|
||||
const agentIdentifier = input.isMain
|
||||
? undefined
|
||||
: group.folder.toLowerCase().replace(/_/g, '-');
|
||||
const containerArgs = await buildContainerArgs(
|
||||
mounts,
|
||||
containerName,
|
||||
agentIdentifier,
|
||||
);
|
||||
const agentIdentifier = input.isMain ? undefined : group.folder.toLowerCase().replace(/_/g, '-');
|
||||
const containerArgs = await buildContainerArgs(mounts, containerName, agentIdentifier);
|
||||
|
||||
logger.debug(
|
||||
{
|
||||
group: group.name,
|
||||
containerName,
|
||||
mounts: mounts.map(
|
||||
(m) =>
|
||||
`${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`,
|
||||
),
|
||||
mounts: mounts.map((m) => `${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`),
|
||||
containerArgs: containerArgs.join(' '),
|
||||
},
|
||||
'Container mount configuration',
|
||||
@@ -372,10 +332,7 @@ export async function runContainerAgent(
|
||||
if (chunk.length > remaining) {
|
||||
stdout += chunk.slice(0, remaining);
|
||||
stdoutTruncated = true;
|
||||
logger.warn(
|
||||
{ group: group.name, size: stdout.length },
|
||||
'Container stdout truncated due to size limit',
|
||||
);
|
||||
logger.warn({ group: group.name, size: stdout.length }, 'Container stdout truncated due to size limit');
|
||||
} else {
|
||||
stdout += chunk;
|
||||
}
|
||||
@@ -389,9 +346,7 @@ export async function runContainerAgent(
|
||||
const endIdx = parseBuffer.indexOf(OUTPUT_END_MARKER, startIdx);
|
||||
if (endIdx === -1) break; // Incomplete pair, wait for more data
|
||||
|
||||
const jsonStr = parseBuffer
|
||||
.slice(startIdx + OUTPUT_START_MARKER.length, endIdx)
|
||||
.trim();
|
||||
const jsonStr = parseBuffer.slice(startIdx + OUTPUT_START_MARKER.length, endIdx).trim();
|
||||
parseBuffer = parseBuffer.slice(endIdx + OUTPUT_END_MARKER.length);
|
||||
|
||||
try {
|
||||
@@ -406,10 +361,7 @@ export async function runContainerAgent(
|
||||
// so idle timers start even for "silent" query completions.
|
||||
outputChain = outputChain.then(() => onOutput(parsed));
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ group: group.name, error: err },
|
||||
'Failed to parse streamed output chunk',
|
||||
);
|
||||
logger.warn({ group: group.name, error: err }, 'Failed to parse streamed output chunk');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,10 +380,7 @@ export async function runContainerAgent(
|
||||
if (chunk.length > remaining) {
|
||||
stderr += chunk.slice(0, remaining);
|
||||
stderrTruncated = true;
|
||||
logger.warn(
|
||||
{ group: group.name, size: stderr.length },
|
||||
'Container stderr truncated due to size limit',
|
||||
);
|
||||
logger.warn({ group: group.name, size: stderr.length }, 'Container stderr truncated due to size limit');
|
||||
} else {
|
||||
stderr += chunk;
|
||||
}
|
||||
@@ -446,17 +395,11 @@ export async function runContainerAgent(
|
||||
|
||||
const killOnTimeout = () => {
|
||||
timedOut = true;
|
||||
logger.error(
|
||||
{ group: group.name, containerName },
|
||||
'Container timeout, stopping gracefully',
|
||||
);
|
||||
logger.error({ group: group.name, containerName }, 'Container timeout, stopping gracefully');
|
||||
try {
|
||||
stopContainer(containerName);
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ group: group.name, containerName, err },
|
||||
'Graceful stop failed, force killing',
|
||||
);
|
||||
logger.warn({ group: group.name, containerName, err }, 'Graceful stop failed, force killing');
|
||||
container.kill('SIGKILL');
|
||||
}
|
||||
};
|
||||
@@ -507,10 +450,7 @@ export async function runContainerAgent(
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error(
|
||||
{ group: group.name, containerName, duration, code },
|
||||
'Container timed out with no output',
|
||||
);
|
||||
logger.error({ group: group.name, containerName, duration, code }, 'Container timed out with no output');
|
||||
|
||||
resolve({
|
||||
status: 'error',
|
||||
@@ -522,8 +462,7 @@ export async function runContainerAgent(
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const logFile = path.join(logsDir, `container-${timestamp}.log`);
|
||||
const isVerbose =
|
||||
process.env.LOG_LEVEL === 'debug' || process.env.LOG_LEVEL === 'trace';
|
||||
const isVerbose = process.env.LOG_LEVEL === 'debug' || process.env.LOG_LEVEL === 'trace';
|
||||
|
||||
const logLines = [
|
||||
`=== Container Run Log ===`,
|
||||
@@ -558,12 +497,7 @@ export async function runContainerAgent(
|
||||
containerArgs.join(' '),
|
||||
``,
|
||||
`=== Mounts ===`,
|
||||
mounts
|
||||
.map(
|
||||
(m) =>
|
||||
`${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`,
|
||||
)
|
||||
.join('\n'),
|
||||
mounts.map((m) => `${m.hostPath} -> ${m.containerPath}${m.readonly ? ' (ro)' : ''}`).join('\n'),
|
||||
``,
|
||||
`=== Stderr${stderrTruncated ? ' (TRUNCATED)' : ''} ===`,
|
||||
stderr,
|
||||
@@ -578,9 +512,7 @@ export async function runContainerAgent(
|
||||
`Session ID: ${input.sessionId || 'new'}`,
|
||||
``,
|
||||
`=== Mounts ===`,
|
||||
mounts
|
||||
.map((m) => `${m.containerPath}${m.readonly ? ' (ro)' : ''}`)
|
||||
.join('\n'),
|
||||
mounts.map((m) => `${m.containerPath}${m.readonly ? ' (ro)' : ''}`).join('\n'),
|
||||
``,
|
||||
);
|
||||
}
|
||||
@@ -612,10 +544,7 @@ export async function runContainerAgent(
|
||||
// Streaming mode: wait for output chain to settle, return completion marker
|
||||
if (onOutput) {
|
||||
outputChain.then(() => {
|
||||
logger.info(
|
||||
{ group: group.name, duration, newSessionId },
|
||||
'Container completed (streaming mode)',
|
||||
);
|
||||
logger.info({ group: group.name, duration, newSessionId }, 'Container completed (streaming mode)');
|
||||
resolve({
|
||||
status: 'success',
|
||||
result: null,
|
||||
@@ -633,9 +562,7 @@ export async function runContainerAgent(
|
||||
|
||||
let jsonLine: string;
|
||||
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
||||
jsonLine = stdout
|
||||
.slice(startIdx + OUTPUT_START_MARKER.length, endIdx)
|
||||
.trim();
|
||||
jsonLine = stdout.slice(startIdx + OUTPUT_START_MARKER.length, endIdx).trim();
|
||||
} else {
|
||||
// Fallback: last non-empty line (backwards compatibility)
|
||||
const lines = stdout.trim().split('\n');
|
||||
@@ -676,10 +603,7 @@ export async function runContainerAgent(
|
||||
|
||||
container.on('error', (err) => {
|
||||
clearTimeout(timeout);
|
||||
logger.error(
|
||||
{ group: group.name, containerName, error: err },
|
||||
'Container spawn error',
|
||||
);
|
||||
logger.error({ group: group.name, containerName, error: err }, 'Container spawn error');
|
||||
resolve({
|
||||
status: 'error',
|
||||
result: null,
|
||||
@@ -708,9 +632,7 @@ export function writeTasksSnapshot(
|
||||
fs.mkdirSync(groupIpcDir, { recursive: true });
|
||||
|
||||
// Main sees all tasks, others only see their own
|
||||
const filteredTasks = isMain
|
||||
? tasks
|
||||
: tasks.filter((t) => t.groupFolder === groupFolder);
|
||||
const filteredTasks = isMain ? tasks : tasks.filter((t) => t.groupFolder === groupFolder);
|
||||
|
||||
const tasksFile = path.join(groupIpcDir, 'current_tasks.json');
|
||||
fs.writeFileSync(tasksFile, JSON.stringify(filteredTasks, null, 2));
|
||||
|
||||
Reference in New Issue
Block a user