fix(pipeline): resolve SafeToAutoRun deadlock and sync freezing (v0.5.20) (#589)

This commit is contained in:
Variet Worker
2026-04-08 07:28:48 +09:00
parent 13f13ee243
commit 2eb1fbb6b7
16 changed files with 821 additions and 61 deletions

View File

@@ -70,12 +70,13 @@ function detectProjectName(): string {
if (folders && folders.length > 0) {
const cwd = folders[0].uri.fsPath;
try {
const remoteUrl = cp.execSync('git remote get-url origin', {
cwd, encoding: 'utf-8', timeout: 2000, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore']
}).toString().trim();
const match = remoteUrl.match(/\/([^\/]+?)(?:\.git)?$/);
if (match && match[1]) {
return match[1].toLowerCase().replace(/[\s\-]+/g, '_');
const gitConfigPath = path.join(cwd, '.git', 'config');
if (fs.existsSync(gitConfigPath)) {
const configContent = fs.readFileSync(gitConfigPath, 'utf8');
const match = configContent.match(/url\s*=\s*.*\/([^\/]+?)(?:\.git)?$/m);
if (match && match[1]) {
return match[1].toLowerCase().replace(/[\s\-]+/g, '_');
}
}
} catch { }
return path.basename(cwd).toLowerCase().replace(/[\s\-]+/g, '_');

View File

@@ -545,10 +545,13 @@ function setupMonitor() {
const toolName = toolCall?.name || stepType.replace('CORTEX_STEP_TYPE_', '').toLowerCase();
let command = toolName;
let isSafeToAutoRun = false;
// Parse argumentsJson for command details
if (toolCall?.argumentsJson) {
try {
const args = JSON.parse(toolCall.argumentsJson);
isSafeToAutoRun = args.SafeToAutoRun === true;
if (args.CommandLine) {
command = `${toolName}: ${args.CommandLine.substring(0, 1500)}`;
} else if (args.TargetFile) {
@@ -563,33 +566,38 @@ function setupMonitor() {
const description = `Step #${si} (${stepType.replace('CORTEX_STEP_TYPE_', '')})`;
ctx.logToFile(`[STEP-PROBE] ★ WAITING! step=${si} type=${stepType} cmd='${command}'`);
if (si !== ctx.lastPendingStepIndex) {
ctx.stallProbed = true; // found WAITING — stop retrying
// Track highest step index for auto-resolve
if (si > ctx.lastPendingStepIndex) {
ctx.lastPendingStepIndex = si;
if (si !== ctx.lastPendingStepIndex) {
ctx.stallProbed = true; // found WAITING — stop retrying
// Track highest step index for auto-resolve
if (si > ctx.lastPendingStepIndex) {
ctx.lastPendingStepIndex = si;
}
lastPendingTime = Date.now();
ctx.sawRunningAfterPending = false;
// Skip pending for workspace-less AG windows (project=default)
if (ctx.projectName === 'default') {
ctx.logToFile(`[STEP-PROBE] skip pending: ctx.projectName=default (no workspace)`);
} else {
// Always write pending — Bot decides auto-approve (prevents double-fire)
writePendingApproval({
conversation_id: ctx.activeSessionId,
command,
description,
step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search'].includes(toolName) ? 'file_permission'
: ['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(toolName) ? 'code_edit'
: ['browser_subagent', 'open_browser_url'].includes(toolName) ? 'browser_subagent'
: toolName,
step_index: si,
source: 'step_probe',
safe_to_auto_run: isSafeToAutoRun,
});
if (isSafeToAutoRun) {
const truncatedCmd = command.length > 500 ? command.substring(0, 500) + '\n...(이하 생략)' : command;
ctx.writeChatSnapshot(`⚡ **자동 실행됨** (step ${si})\n\n\`\`\`\n${truncatedCmd}\n\`\`\``);
}
}
}
lastPendingTime = Date.now();
ctx.sawRunningAfterPending = false;
// Skip pending for workspace-less AG windows (project=default)
if (ctx.projectName === 'default') {
ctx.logToFile(`[STEP-PROBE] skip pending: ctx.projectName=default (no workspace)`);
} else {
// Always write pending — Bot decides auto-approve (prevents double-fire)
writePendingApproval({
conversation_id: ctx.activeSessionId,
command,
description,
step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search'].includes(toolName) ? 'file_permission'
: ['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(toolName) ? 'code_edit'
: ['browser_subagent', 'open_browser_url'].includes(toolName) ? 'browser_subagent'
: toolName,
step_index: si,
source: 'step_probe',
});
}
}
// NOTE: no break — process ALL parallel WAITING steps
// NOTE: no break — process ALL parallel WAITING steps
}
}
if (!foundWaiting) {
@@ -626,9 +634,11 @@ function setupMonitor() {
const toolCall = oStep?.metadata?.toolCall;
const toolName = toolCall?.name || (oStep.type || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase();
let command = toolName;
let isSafeToAutoRun = false;
if (toolCall?.argumentsJson) {
try {
const args = JSON.parse(toolCall.argumentsJson);
isSafeToAutoRun = args.SafeToAutoRun === true;
if (args.CommandLine) command = `${toolName}: ${args.CommandLine.substring(0, 1500)}`;
else if (args.TargetFile) command = `${toolName}: ${args.TargetFile}`;
else {
@@ -655,7 +665,12 @@ function setupMonitor() {
: toolName,
step_index: actualIndex,
source: 'step_probe_utf8_offset',
safe_to_auto_run: isSafeToAutoRun,
});
if (isSafeToAutoRun) {
const truncatedCmd = command.length > 500 ? command.substring(0, 500) + '\n...(이하 생략)' : command;
ctx.writeChatSnapshot(`⚡ **자동 실행됨** (step ${actualIndex})\n\n\`\`\`\n${truncatedCmd}\n\`\`\``);
}
}
}
// NOTE: no break — process ALL parallel WAITING steps
@@ -1024,7 +1039,7 @@ function setupMonitor() {
/** Write a pending approval file matching Bot's ApprovalRequest dataclass. */
export function writePendingApproval(data: { conversation_id: string; command: string; description: string; step_type?: string; step_index?: number; source?: string; buttons?: Array<{ text: string; index: number }>; modified_files?: string[]; edit_step_indices?: number[] }) {
export function writePendingApproval(data: { conversation_id: string; command: string; description: string; step_type?: string; step_index?: number; source?: string; buttons?: Array<{ text: string; index: number }>; modified_files?: string[]; edit_step_indices?: number[]; safe_to_auto_run?: boolean }) {
try {
const pendingDir = path.join(ctx.bridgePath, 'pending');
if (!fs.existsSync(pendingDir)) { fs.mkdirSync(pendingDir, { recursive: true }); }
@@ -1063,6 +1078,7 @@ export function writePendingApproval(data: { conversation_id: string; command: s
existing.description = data.description;
if (data.step_type) existing.step_type = data.step_type;
if (data.step_index !== undefined) existing.step_index = data.step_index;
if (data.safe_to_auto_run !== undefined) existing.safe_to_auto_run = data.safe_to_auto_run;
existing.source = 'dom_observer+step_probe'; // mark as merged
fs.promises.writeFile(efPath, JSON.stringify(existing, null, 2), 'utf-8').catch(e => {
ctx.logToFile(`[DEDUP] merge write error: ${e.message}`);