feat(enrichment): Discord 알림 지연 + Step Probe 폴링 — generic Always run 커맨드 100% 보강 (v0.5.91)

This commit is contained in:
Variet Worker
2026-04-19 10:25:55 +09:00
parent bf53072f3c
commit bb54802c06
5 changed files with 168 additions and 16 deletions

View File

@@ -345,7 +345,7 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
}
const rid = data.request_id || Date.now().toString();
ctx.logToFile(`[HTTP] AUTO-APPROVE "Always run" (btnIdx=${alwaysRunBtnIndex}): cmd="${displayCmd.substring(0, 80)}"`);
// Write response file so observer's pollResponseGroup picks it up and clicks the button
// Write response file IMMEDIATELY so observer clicks the button with zero delay
const responseDir = path.join(ctx.bridgePath, 'response');
if (!fs.existsSync(responseDir)) {
fs.mkdirSync(responseDir, { recursive: true });
@@ -362,20 +362,48 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
JSON.stringify(respPayload),
'utf-8'
);
// Notify Discord (non-interactive "자동 승인" embed)
if (ctx.wsBridge && ctx.wsBridge.isConnected()) {
ctx.wsBridge.sendPending({
request_id: rid,
command: displayCmd,
description: rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd,
step_type: data.step_type || 'command',
status: 'auto_approved',
buttons: data.buttons,
project_name: ctx.projectName,
});
}
// v29: Respond to Observer immediately (don't block button click)
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, request_id: rid, auto_approved: true }));
// v29: Discord notification — if displayCmd is generic, poll Step Probe for real command
const isGenericDisplay = GENERIC_BTN_RE.test(displayCmd);
const sendDiscord = (finalCmd: string, finalDesc: string) => {
if (ctx.wsBridge && ctx.wsBridge.isConnected()) {
ctx.wsBridge.sendPending({
request_id: rid,
command: finalCmd,
description: finalDesc,
step_type: data.step_type || 'command',
status: 'auto_approved',
buttons: data.buttons,
project_name: ctx.projectName,
});
}
};
if (isGenericDisplay && ctx.getLastWaitingCommand) {
// Poll Step Probe memory for up to 6s (API polls every 5s)
let pollAttempt = 0;
const maxAttempts = 30; // 30 * 200ms = 6s
const pollTimer = setInterval(() => {
pollAttempt++;
const wc = ctx.getLastWaitingCommand!();
if (wc.cmd && wc.cmd.length > 3 && !GENERIC_BTN_RE.test(wc.cmd) && (Date.now() - wc.ts) < 15_000) {
clearInterval(pollTimer);
const enrichedCmd = wc.desc && wc.desc.length > wc.cmd.length ? wc.desc.substring(0, 200) : wc.cmd.substring(0, 200);
ctx.logToFile(`[HTTP] AUTO-APPROVE enriched (delayed ${pollAttempt * 200}ms): "${enrichedCmd.substring(0, 80)}"`);
sendDiscord(enrichedCmd, `[${rawCmd}] ${enrichedCmd}`);
} else if (pollAttempt >= maxAttempts) {
clearInterval(pollTimer);
ctx.logToFile(`[HTTP] AUTO-APPROVE no enrichment after ${maxAttempts * 200}ms — sending generic`);
sendDiscord(displayCmd, rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd);
}
}, 200);
} else {
// Already enriched or no Step Probe — send immediately
sendDiscord(displayCmd, rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd);
}
return;
}
if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) {