chore: exclude local resource artifacts from main sync

This commit is contained in:
2026-05-15 10:16:45 +09:00
parent 442879313f
commit d38d022872
504 changed files with 17074 additions and 3642 deletions

View File

@@ -51,7 +51,100 @@ const CODEX_HOME_RUNTIME_PATHS = [
'version.json',
];
const CHAT_SESSION_RESOURCE_DIR_MODE = 0o777;
const CODEX_LIVE_EVENT_HISTORY_LIMIT = 400;
const CODEX_LIVE_FINISHED_RETENTION_MS = Math.max(
60_000,
Number(process.env.SERVER_COMMAND_RUNNER_CODEX_FINISHED_RETENTION_MS?.trim() || `${10 * 60 * 1000}`),
);
const activeCodexExecutions = new Map();
const recentCodexExecutions = new Map();
function createCodexExecutionRecord({ requestId, child, tempDir }) {
return {
requestId,
child,
tempDir,
subscribers: new Set(),
history: [],
completed: false,
cleanupTimer: null,
};
}
function registerSubscriberClose(record, response) {
response.on('close', () => {
record.subscribers.delete(response);
});
}
function attachCodexExecutionSubscriber(record, response) {
response.writeHead(200, {
'content-type': 'application/x-ndjson; charset=utf-8',
'cache-control': 'no-store',
});
record.subscribers.add(response);
registerSubscriberClose(record, response);
for (const payload of record.history) {
sendJsonLine(response, payload);
}
if (record.completed) {
response.end();
record.subscribers.delete(response);
}
}
function broadcastCodexExecutionEvent(record, payload) {
record.history.push(payload);
if (record.history.length > CODEX_LIVE_EVENT_HISTORY_LIMIT) {
record.history.splice(0, record.history.length - CODEX_LIVE_EVENT_HISTORY_LIMIT);
}
for (const response of record.subscribers) {
try {
sendJsonLine(response, payload);
} catch {
record.subscribers.delete(response);
}
}
}
function closeCodexExecutionSubscribers(record) {
for (const response of record.subscribers) {
try {
response.end();
} catch {
// noop
}
}
record.subscribers.clear();
}
function scheduleCodexExecutionCleanup(record) {
if (record.cleanupTimer) {
clearTimeout(record.cleanupTimer);
}
record.cleanupTimer = setTimeout(async () => {
recentCodexExecutions.delete(record.requestId);
await rm(record.tempDir, { recursive: true, force: true }).catch(() => undefined);
}, CODEX_LIVE_FINISHED_RETENTION_MS);
record.cleanupTimer.unref?.();
}
function finalizeCodexExecution(record) {
if (record.completed) {
return;
}
record.completed = true;
activeCodexExecutions.delete(record.requestId);
recentCodexExecutions.set(record.requestId, record);
closeCodexExecutionSubscribers(record);
scheduleCodexExecutionCleanup(record);
}
async function ensureWorldWritableDirectory(absolutePath) {
await mkdir(absolutePath, { recursive: true, mode: CHAT_SESSION_RESOURCE_DIR_MODE });
@@ -559,6 +652,18 @@ async function runCodexLiveExecution(payload, response) {
return;
}
const existingExecution = activeCodexExecutions.get(requestId) ?? recentCodexExecutions.get(requestId);
if (existingExecution) {
attachCodexExecutionSubscriber(existingExecution, response);
broadcastCodexExecutionEvent(existingExecution, {
type: 'attached',
requestId,
completed: existingExecution.completed,
});
return;
}
await validateCodexExecutionRuntime(repoPath, codexBin);
await ensureWritableChatSessionDirectories(repoPath, sessionId);
@@ -568,16 +673,10 @@ async function runCodexLiveExecution(payload, response) {
let stderrTail = '';
let jsonLineBuffer = '';
let completedText = '';
let responseClosed = false;
let idleTimer = null;
let executionTimer = null;
let terminationRequested = false;
response.writeHead(200, {
'content-type': 'application/x-ndjson; charset=utf-8',
'cache-control': 'no-store',
});
const child = spawn(
codexBin,
['exec', '--model', CODEX_LIVE_MODEL, '--json', '--dangerously-bypass-approvals-and-sandbox', '-C', repoPath, '-'],
@@ -594,22 +693,20 @@ async function runCodexLiveExecution(payload, response) {
},
);
activeCodexExecutions.set(requestId, {
const executionRecord = createCodexExecutionRecord({
requestId,
child,
tempDir,
});
sendJsonLine(response, {
activeCodexExecutions.set(requestId, executionRecord);
attachCodexExecutionSubscriber(executionRecord, response);
broadcastCodexExecutionEvent(executionRecord, {
type: 'started',
pid: child.pid ?? null,
configuredIdleTimeoutSeconds: Math.round(configuredIdleTimeoutMs / 1000),
configuredMaxExecutionSeconds: Math.round(configuredMaxExecutionMs / 1000),
});
const cleanup = async () => {
activeCodexExecutions.delete(requestId);
await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
};
const clearExecutionTimers = () => {
if (idleTimer) {
clearTimeout(idleTimer);
@@ -630,14 +727,10 @@ async function runCodexLiveExecution(payload, response) {
terminationRequested = true;
clearExecutionTimers();
if (!responseClosed) {
sendJsonLine(response, {
type: 'error',
message,
});
response.end();
responseClosed = true;
}
broadcastCodexExecutionEvent(executionRecord, {
type: 'error',
message,
});
child.kill('SIGTERM');
setTimeout(() => {
@@ -685,7 +778,7 @@ async function runCodexLiveExecution(payload, response) {
if (activityLog) {
refreshIdleTimer();
sendJsonLine(response, {
broadcastCodexExecutionEvent(executionRecord, {
type: 'activity',
line: activityLog,
});
@@ -696,7 +789,7 @@ async function runCodexLiveExecution(payload, response) {
if (nextCompletedText) {
refreshIdleTimer();
completedText = nextCompletedText;
sendJsonLine(response, {
broadcastCodexExecutionEvent(executionRecord, {
type: 'completed',
text: nextCompletedText,
});
@@ -705,7 +798,7 @@ async function runCodexLiveExecution(payload, response) {
if (deltaText) {
refreshIdleTimer();
sendJsonLine(response, {
broadcastCodexExecutionEvent(executionRecord, {
type: 'delta',
text: deltaText,
});
@@ -715,11 +808,6 @@ async function runCodexLiveExecution(payload, response) {
return false;
};
response.on('close', () => {
responseClosed = true;
clearExecutionTimers();
});
child.stdout?.on('data', (chunk) => {
refreshIdleTimer();
const text = String(chunk);
@@ -740,7 +828,7 @@ async function runCodexLiveExecution(payload, response) {
}
if (!line.startsWith('{') && !isIgnorableCodexDiagnosticLine(line)) {
sendJsonLine(response, {
broadcastCodexExecutionEvent(executionRecord, {
type: 'stdout',
line,
});
@@ -761,7 +849,7 @@ async function runCodexLiveExecution(payload, response) {
return;
}
sendJsonLine(response, {
broadcastCodexExecutionEvent(executionRecord, {
type: 'stderr',
line,
});
@@ -770,15 +858,15 @@ async function runCodexLiveExecution(payload, response) {
child.on('error', async (error) => {
clearExecutionTimers();
if (!responseClosed) {
sendJsonLine(response, {
type: 'error',
message: error instanceof Error ? error.message : String(error),
});
response.end();
}
await cleanup();
broadcastCodexExecutionEvent(executionRecord, {
type: 'error',
message: error instanceof Error ? error.message : String(error),
});
broadcastCodexExecutionEvent(executionRecord, {
type: 'finished',
exitCode: null,
});
finalizeCodexExecution(executionRecord);
});
child.on('close', async (code) => {
@@ -788,18 +876,18 @@ async function runCodexLiveExecution(payload, response) {
handleCodexJsonLine(trailingLine);
}
if (!responseClosed) {
if (code !== 0) {
sendJsonLine(response, {
type: 'error',
message: summarizeCodexOutput(`${stderrTail}\n${completedText}\n${stdoutTail}`),
});
}
response.end();
if (code !== 0) {
broadcastCodexExecutionEvent(executionRecord, {
type: 'error',
message: summarizeCodexOutput(`${stderrTail}\n${completedText}\n${stdoutTail}`),
});
}
await cleanup();
broadcastCodexExecutionEvent(executionRecord, {
type: 'finished',
exitCode: code ?? null,
});
finalizeCodexExecution(executionRecord);
});
child.stdin?.end(prompt);
@@ -958,6 +1046,28 @@ const server = createServer(async (request, response) => {
return;
}
const attachMatch = requestUrl.pathname.match(/^\/api\/codex-live\/jobs\/([^/]+)\/attach$/);
if (request.method === 'POST' && attachMatch) {
const requestId = decodeURIComponent(attachMatch[1]);
const execution = activeCodexExecutions.get(requestId) ?? recentCodexExecutions.get(requestId);
if (!execution) {
sendJson(response, 404, {
attached: false,
message: '재부착할 Codex 작업을 찾지 못했습니다.',
});
return;
}
attachCodexExecutionSubscriber(execution, response);
broadcastCodexExecutionEvent(execution, {
type: 'attached',
requestId,
completed: execution.completed,
});
return;
}
const cancelMatch = requestUrl.pathname.match(/^\/api\/codex-live\/jobs\/([^/]+)\/cancel$/);
if (request.method === 'POST' && cancelMatch) {
const requestId = decodeURIComponent(cancelMatch[1]);