fix(ext,bot): Accept All WS regression + auto_approve dual-write — VSIX v0.4.5
This commit is contained in:
@@ -22,6 +22,18 @@
|
|||||||
|
|
||||||
## 공통 이슈
|
## 공통 이슈
|
||||||
|
|
||||||
|
### [2026-03-17] diff_review Accept All WS 경로 regression (v0.4.5 fix)
|
||||||
|
- **증상**: Discord에서 Accept all 클릭해도 AG에서 반응 없음
|
||||||
|
- **원인**: v0.4.0 WS 전환 시 `onResponse` 핸들러에 `diff_review` step_type 분기가 누락됨. `processResponseFile` (파일 경로)에만 diff_review 로직이 있었고, WS `onResponse`는 무조건 `tryApprovalStrategies()`만 호출. `tryApprovalStrategies`는 diff_review를 처리하지 않음
|
||||||
|
- **해결**: `step-probe.ts`에 `handleDiffReviewResponse()` export 함수 추출. `extension.ts` `onResponse`에서 `stepType === 'diff_review'`일 때 이 함수 호출
|
||||||
|
- **주의**: **WS 경로 추가 시 file-bridge 경로의 모든 분기를 반드시 포팅할 것**. step_type별 분기(diff_review, file_permission 등)가 누락되기 쉬움
|
||||||
|
|
||||||
|
### [2026-03-17] _auto_approve_via_hub 이중 쓰기 (v0.4.5 fix)
|
||||||
|
- **증상**: auto-approve 시 Extension에 응답이 2번 도착
|
||||||
|
- **원인**: `bot.py:_auto_approve_via_hub()` L1095-1100에서 Hub WS 전송 후 `return` 없이 file bridge에도 씀
|
||||||
|
- **해결**: `if self.hub:` → WS 전송 + `else:` → file bridge. if/else 구조로 변경
|
||||||
|
- **주의**: Hub WS와 file bridge는 **항상 상호 배타적**이어야 함. `return`이 아닌 `if/else` 사용 권장
|
||||||
|
|
||||||
### [2026-03-08] PowerShell curl — Invoke-WebRequest 충돌
|
### [2026-03-08] PowerShell curl — Invoke-WebRequest 충돌
|
||||||
- **증상**: `curl` 명령이 예상과 다른 응답 형식을 반환
|
- **증상**: `curl` 명령이 예상과 다른 응답 형식을 반환
|
||||||
- **원인**: PowerShell에서 `curl`은 `Invoke-WebRequest`의 별칭
|
- **원인**: PowerShell에서 `curl`은 `Invoke-WebRequest`의 별칭
|
||||||
|
|||||||
4
bot.py
4
bot.py
@@ -1092,7 +1092,9 @@ class GravityBot(commands.Bot):
|
|||||||
"project_name": request.project_name,
|
"project_name": request.project_name,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
# Also write via legacy bridge
|
# Hub delivered — skip file bridge to prevent dual delivery
|
||||||
|
else:
|
||||||
|
# File bridge fallback (only when Hub is unavailable)
|
||||||
self.bridge.write_response(UserResponse(
|
self.bridge.write_response(UserResponse(
|
||||||
request_id=request.request_id, approved=True,
|
request_id=request.request_id, approved=True,
|
||||||
step_type=request.step_type,
|
step_type=request.step_type,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
| 012 | 09:00~17:44 | VSIX E2E: workspaceUri, 이중발송, ApprovalRequest, ApprovalView WS, 응답 라우팅 | `2eea5fa` | ✅ |
|
| 012 | 09:00~17:44 | VSIX E2E: workspaceUri, 이중발송, ApprovalRequest, ApprovalView WS, 응답 라우팅 | `2eea5fa` | ✅ |
|
||||||
| 013 | 18:05~18:45 | Extension 모듈 분리 #398: http-bridge, html-patcher, command-handler 추출 (1296→650줄) | `6640d42` | ✅ |
|
| 013 | 18:05~18:45 | Extension 모듈 분리 #398: http-bridge, html-patcher, command-handler 추출 (1296→650줄) | `6640d42` | ✅ |
|
||||||
| 014 | 18:45~20:35 | WS+File dual-delivery 수정 + 에코 릴레이 수정 + VSIX v0.4.4 빌드 | `0da6291` | ✅ |
|
| 014 | 18:45~20:35 | WS+File dual-delivery 수정 + 에코 릴레이 수정 + VSIX v0.4.4 빌드 | `0da6291` | ✅ |
|
||||||
|
| 015 | 20:45~21:00 | Accept All WS regression 수정 + auto_approve 이중쓰기 수정 + VSIX v0.4.5 | — | 🔧 |
|
||||||
|
|
||||||
### #010 상세
|
### #010 상세
|
||||||
- **문서**: architecture.md(250줄), tech-stack.md(100줄), conventions.md(100줄) 전면 재작성 + Wiki 동기화
|
- **문서**: architecture.md(250줄), tech-stack.md(100줄), conventions.md(100줄) 전면 재작성 + Wiki 동기화
|
||||||
|
|||||||
@@ -402,9 +402,27 @@ async function activate(context) {
|
|||||||
wsBridge = new ws_client_1.WSBridgeClient(hubUrl, regCode, projectName, pcName, {
|
wsBridge = new ws_client_1.WSBridgeClient(hubUrl, regCode, projectName, pcName, {
|
||||||
onResponse: (data) => {
|
onResponse: (data) => {
|
||||||
logToFile(`[WS-RESPONSE] ${data.request_id?.substring(0, 12)} approved=${data.approved} step_type=${data.step_type || '(none)'}`);
|
logToFile(`[WS-RESPONSE] ${data.request_id?.substring(0, 12)} approved=${data.approved} step_type=${data.step_type || '(none)'}`);
|
||||||
// Direct approval — WS path has no pending file, so call tryApprovalStrategies directly
|
|
||||||
const approved = data.approved ?? true;
|
const approved = data.approved ?? true;
|
||||||
const stepType = data.step_type || '';
|
const stepType = data.step_type || '';
|
||||||
|
// ── diff_review: Accept all / Reject all (REGRESSION FIX) ──
|
||||||
|
// Previously only handled in processResponseFile (file-bridge path).
|
||||||
|
// WS path was missing this logic entirely, causing Accept All to fail.
|
||||||
|
if (stepType === 'diff_review') {
|
||||||
|
logToFile(`[WS-RESPONSE] diff_review detected — routing to handleDiffReviewResponse`);
|
||||||
|
(0, step_probe_1.handleDiffReviewResponse)({
|
||||||
|
request_id: data.request_id,
|
||||||
|
approved,
|
||||||
|
button_index: data.button_index,
|
||||||
|
step_type: stepType,
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
logToFile(`[WS-RESPONSE] diff_review result: ${result}`);
|
||||||
|
(0, step_probe_1.resetPendingState)();
|
||||||
|
})
|
||||||
|
.catch(err => logToFile(`[WS-RESPONSE] diff_review error: ${err.message}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Normal approval — tryApprovalStrategies
|
||||||
const approvalCtx = (0, step_probe_1.getApprovalContext)();
|
const approvalCtx = (0, step_probe_1.getApprovalContext)();
|
||||||
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
||||||
(0, step_probe_1.tryApprovalStrategies)(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
|
(0, step_probe_1.tryApprovalStrategies)(approved, approvalCtx.sessionId, stepType, approvalCtx.stepIndex)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
|||||||
"name": "gravity-bridge",
|
"name": "gravity-bridge",
|
||||||
"displayName": "Gravity Bridge",
|
"displayName": "Gravity Bridge",
|
||||||
"description": "Antigravity ↔ Discord 브리지 연동 확장",
|
"description": "Antigravity ↔ Discord 브리지 연동 확장",
|
||||||
"version": "0.4.4",
|
"version": "0.4.5",
|
||||||
"publisher": "variet",
|
"publisher": "variet",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.100.0"
|
"vscode": "^1.100.0"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import * as path from 'path';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as cp from 'child_process';
|
import * as cp from 'child_process';
|
||||||
import { WSBridgeClient, WSResponseData, WSCommandData } from './ws-client';
|
import { WSBridgeClient, WSResponseData, WSCommandData } from './ws-client';
|
||||||
import { initStepProbe, BridgeContext, writePendingApproval, tryApprovalStrategies, writeRegistration, getApprovalContext, resetPendingState } from './step-probe';
|
import { initStepProbe, BridgeContext, writePendingApproval, tryApprovalStrategies, writeRegistration, getApprovalContext, resetPendingState, handleDiffReviewResponse } from './step-probe';
|
||||||
import { startHttpBridge, getDeterministicPort, HttpBridgeContext } from './http-bridge';
|
import { startHttpBridge, getDeterministicPort, HttpBridgeContext } from './http-bridge';
|
||||||
import { setupApprovalObserver } from './html-patcher';
|
import { setupApprovalObserver } from './html-patcher';
|
||||||
import { watchCommandsDir, handleWSCommand, disposeCommandsWatcher, CommandHandlerContext } from './command-handler';
|
import { watchCommandsDir, handleWSCommand, disposeCommandsWatcher, CommandHandlerContext } from './command-handler';
|
||||||
@@ -391,9 +391,29 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
if (hubUrl) {
|
if (hubUrl) {
|
||||||
wsBridge = new WSBridgeClient(hubUrl, regCode, projectName, pcName, {
|
wsBridge = new WSBridgeClient(hubUrl, regCode, projectName, pcName, {
|
||||||
onResponse: (data: WSResponseData) => {
|
onResponse: (data: WSResponseData) => {
|
||||||
logToFile(`[WS-RESPONSE] ${data.request_id?.substring(0, 12)} approved=${data.approved} step_type=${data.step_type || '(none)'}`);
|
|
||||||
logToFile(`[WS-RESPONSE] ${data.request_id?.substring(0, 12)} approved=${data.approved} step_type=${data.step_type || '(none)'}`);
|
logToFile(`[WS-RESPONSE] ${data.request_id?.substring(0, 12)} approved=${data.approved} step_type=${data.step_type || '(none)'}`);
|
||||||
const approved = data.approved ?? true;
|
const approved = data.approved ?? true;
|
||||||
|
const stepType = data.step_type || '';
|
||||||
|
|
||||||
|
// ── diff_review: Accept all / Reject all (REGRESSION FIX) ──
|
||||||
|
// Previously only handled in processResponseFile (file-bridge path).
|
||||||
|
// WS path was missing this logic entirely, causing Accept All to fail.
|
||||||
|
if (stepType === 'diff_review') {
|
||||||
|
logToFile(`[WS-RESPONSE] diff_review detected — routing to handleDiffReviewResponse`);
|
||||||
|
handleDiffReviewResponse({
|
||||||
|
request_id: data.request_id,
|
||||||
|
approved,
|
||||||
|
button_index: data.button_index,
|
||||||
|
step_type: stepType,
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
logToFile(`[WS-RESPONSE] diff_review result: ${result}`);
|
||||||
|
resetPendingState();
|
||||||
|
})
|
||||||
|
.catch(err => logToFile(`[WS-RESPONSE] diff_review error: ${err.message}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Normal approval — tryApprovalStrategies
|
// Normal approval — tryApprovalStrategies
|
||||||
const approvalCtx = getApprovalContext();
|
const approvalCtx = getApprovalContext();
|
||||||
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
logToFile(`[WS-RESPONSE] Triggering approval: approved=${approved} session=${approvalCtx.sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${approvalCtx.stepIndex}`);
|
||||||
|
|||||||
@@ -61,6 +61,100 @@ export function resetPendingState(): void {
|
|||||||
ctx.sawRunningAfterPending = false;
|
ctx.sawRunningAfterPending = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle diff_review Accept all / Reject all response.
|
||||||
|
* Extracted so both WS onResponse (extension.ts) and processResponseFile can call it.
|
||||||
|
*
|
||||||
|
* This was previously only in processResponseFile (file-bridge path).
|
||||||
|
* When WS was added (v0.4.x), the onResponse handler skipped this logic entirely,
|
||||||
|
* causing Accept All to stop working — a regression.
|
||||||
|
*/
|
||||||
|
export async function handleDiffReviewResponse(data: {
|
||||||
|
request_id: string;
|
||||||
|
approved: boolean;
|
||||||
|
button_index?: number;
|
||||||
|
step_type?: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
const btnIdx = data.button_index ?? -1;
|
||||||
|
const isAccept = btnIdx === 0 || (btnIdx === -1 && data.approved);
|
||||||
|
const cmd = isAccept
|
||||||
|
? 'antigravity.prioritized.agentAcceptAllInFile'
|
||||||
|
: 'antigravity.prioritized.agentRejectAllInFile';
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] → ${isAccept ? 'ACCEPT' : 'REJECT'} (btnIdx=${btnIdx}, rid=${data.request_id?.substring(0, 12)})`);
|
||||||
|
|
||||||
|
let diffReviewDone = false;
|
||||||
|
let modifiedFiles: string[] = [];
|
||||||
|
|
||||||
|
// Load tracked step indices and modified files from memory cache or pending file
|
||||||
|
const trackedSteps: number[] = [];
|
||||||
|
const memMeta = ctx.diffReviewMetadata.get(data.request_id);
|
||||||
|
if (memMeta) {
|
||||||
|
trackedSteps.push(...memMeta.edit_step_indices);
|
||||||
|
modifiedFiles = memMeta.modified_files;
|
||||||
|
ctx.diffReviewMetadata.delete(data.request_id);
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] loaded from memory: steps=[${trackedSteps.join(',')}] files=${modifiedFiles.length}`);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const pf = path.join(ctx.bridgePath, 'pending', `${data.request_id}.json`);
|
||||||
|
if (fs.existsSync(pf)) {
|
||||||
|
const pd = JSON.parse(fs.readFileSync(pf, 'utf-8'));
|
||||||
|
if (pd.edit_step_indices) trackedSteps.push(...pd.edit_step_indices);
|
||||||
|
if (pd.modified_files) modifiedFiles = pd.modified_files;
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 1: VS Code command — open review panel + focus each file + accept/reject
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
await vscode.commands.executeCommand('antigravity.openReviewChanges');
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] openReviewChanges OK`);
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
if (modifiedFiles.length > 0) {
|
||||||
|
for (const fp of modifiedFiles) {
|
||||||
|
try {
|
||||||
|
const uri = vscode.Uri.file(fp);
|
||||||
|
const doc = await vscode.workspace.openTextDocument(uri);
|
||||||
|
await vscode.window.showTextDocument(doc, { preview: false });
|
||||||
|
await new Promise(r => setTimeout(r, 300));
|
||||||
|
await vscode.commands.executeCommand(cmd);
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] ✅ ${cmd} on ${fp.split(/[\\/]/).pop()} OK`);
|
||||||
|
diffReviewDone = true;
|
||||||
|
} catch (e: any) {
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] per-file error on ${fp}: ${e.message?.substring(0, 80)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await vscode.commands.executeCommand(cmd);
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] ✅ ${cmd} executed (no file list)`);
|
||||||
|
diffReviewDone = true;
|
||||||
|
}
|
||||||
|
} catch (cmdErr: any) {
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] Strategy 1 command error: ${cmdErr.message?.substring(0, 200)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 2: individual hunk accept/reject
|
||||||
|
if (!diffReviewDone) {
|
||||||
|
try {
|
||||||
|
const hunkCmd = isAccept
|
||||||
|
? 'antigravity.prioritized.agentAcceptFocusedHunk'
|
||||||
|
: 'antigravity.prioritized.agentRejectFocusedHunk';
|
||||||
|
await vscode.commands.executeCommand(hunkCmd);
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] ✅ ${hunkCmd} fallback OK`);
|
||||||
|
diffReviewDone = true;
|
||||||
|
} catch (hunkErr: any) {
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] hunk fallback error: ${hunkErr.message?.substring(0, 100)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!diffReviewDone) {
|
||||||
|
ctx.logToFile(`[DIFF-REVIEW-WS] ❌ ALL strategies failed for rid=${data.request_id}`);
|
||||||
|
}
|
||||||
|
return diffReviewDone;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a registration file for the Bot to discover session → project mapping.
|
* Write a registration file for the Bot to discover session → project mapping.
|
||||||
* Called automatically on first step event per session.
|
* Called automatically on first step event per session.
|
||||||
|
|||||||
Reference in New Issue
Block a user