Compare commits
13 Commits
64800d3c20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32cf69469c | ||
|
|
7c8891b99c | ||
|
|
3cc3442fda | ||
|
|
e95e7791f9 | ||
|
|
2bf1eb41d1 | ||
|
|
cf1352eefa | ||
|
|
6aea48e2e9 | ||
|
|
bd5a7ca8b9 | ||
|
|
8ada5f7daf | ||
|
|
4f2be831a1 | ||
|
|
cbfd137dcb | ||
|
|
a99a1e3f54 | ||
|
|
ad4ed623bd |
@@ -21,6 +21,18 @@
|
||||
> 鍮꾩듂븳 臾몄젣媛 옱諛쒗븯硫 archive뿉꽌 寃깋븯꽭슂.
|
||||
|
||||
|
||||
### [2026-04-19] [Observer] ★ Accept all 버튼이 `<span>`으로 렌더링 — Observer 감지 실패 (v0.5.101)
|
||||
- **증상**: "Accept all" diff review 버튼이 화면에 보이지만 Observer가 감지하지 못함. Discord에 "Accept all" 자동 승인 알림이 안 옴.
|
||||
- **원인**: AG Native UI 업데이트로 "Accept all"이 `<button>`이 아닌 `<span class="cursor-pointer">`로 렌더링됨. Observer의 `allBtns = querySelectorAll('button, [role="button"]')`에 span이 포함되지 않음. ACCEPT-SCAN 로그: `tag=SPAN cls=hover:text-ide-button-hover-color cursor-po txt=Accept all`.
|
||||
- **해결 (v0.5.101)**: `allBtns` 선택자에 `span.cursor-pointer` 추가.
|
||||
- **주의**: observer-dev-guide 섹션 3.3 "Accept all — Observer 접근 불가"는 outdated. UI 변경으로 chat panel footer에 Accept all이 표시됨. 가이드 업데이트 필요.
|
||||
|
||||
### [2026-04-19] [Bridge] ★ auto-approve response 파일에 `_from_ws` 마커 누락 — Observer polling 실패 (v0.5.103)
|
||||
- **증상**: Observer가 "Accept all"을 감지하고 bridge가 자동 승인했지만, Observer의 `pollResponseGroup` GET `/response/{rid}`가 항상 `{waiting: true}` 반환. 버튼 클릭이 실행되지 않음.
|
||||
- **원인**: http-bridge의 auto-approve 경로에서 response JSON 파일에 `_from_ws: true` 마커가 없음 → `processResponseFile`(response watcher)이 Observer보다 먼저 파일을 읽고 삭제 → Observer polling 시 파일 부재. known-issues [2026-04-18] WS response 삭제 버그와 동일 패턴.
|
||||
- **해결 (v0.5.103)**: auto-approve response에 `_from_ws: true` + `_auto_approve_ttl` 마커 추가.
|
||||
- **주의**: **response 디렉토리에 파일을 쓰는 모든 경로**는 반드시 `_from_ws: true` 마커를 포함해야 함. processResponseFile이 먼저 소비하는 race condition 항상 존재.
|
||||
|
||||
### [2026-04-18] [Extension] ★ WS response 파일이 processResponseFile에 의해 삭제 → Observer pollResponseGroup 실패 (v0.5.78)
|
||||
- **증상**: `!auto` Retry가 작동하지 않음. Observer가 `/response/{rid}`를 폴링하지만 항상 `{waiting: true}` 반환.
|
||||
- **원인**: extension.ts의 WS 응답 핸들러가 `response/{rid}.json` 파일 작성 → 300ms 후 response watcher(`processResponseFile`)가 파일 감지 → pending 파일이 없어 `isDomObserver=false` → `fs.unlinkSync()` 실행 → Observer가 폴링할 때 파일이 이미 삭제됨.
|
||||
|
||||
@@ -38,10 +38,14 @@ npm.cmd run compile; node -e "const {generateApprovalObserverScript}=require('./
|
||||
4. [ ] **변경 범위 최소화**: 불필요한 코드 포함 여부 확인
|
||||
5. [ ] **재시작 사유 명시**: 사용자에게 (a) 무엇을 수정했고 (b) 왜 재시작이 필요한지 1~2줄로 설명
|
||||
6. [ ] **재시작 횟수 명시**: Observer 변경 = 2회, Extension host만 변경 = 1회
|
||||
7. [ ] **log() relay 필터 확인**: 새 로그 키워드 추가 시 log() 함수의 키워드 필터에도 추가했는지 확인 (섹션 3.5 참조)
|
||||
8. [ ] **regex E2E 테스트**: Observer에서 사용하는 새 regex는 생성된 코드에서 직접 실행하여 매칭 검증
|
||||
9. [ ] **구현 전 가정 검증**: 새 접근을 코딩하기 전에, 핵심 가정이 성립하는지 로그 1줄로 먼저 확인 (예: "Step Probe가 WAITING을 볼 수 있는가?" → `STEP-PROBE.*WAITING` 로그 검색)
|
||||
|
||||
**정당한 사유 없이 재시작을 요구하지 않는다.**
|
||||
**DOM 구조를 먼저 파악하고 설계한 후 코드를 작성한다.**
|
||||
**시행착오식(trial-and-error) 접근을 하지 않는다.**
|
||||
**추측으로 코딩하지 않는다. 로그/데이터로 확인한 사실에 기반하여 코딩한다.**
|
||||
|
||||
---
|
||||
|
||||
@@ -85,21 +89,41 @@ Select-String -Path "$env:LOCALAPPDATA\Programs\Antigravity\resources\app\out\vs
|
||||
- 사용자 메시지 블록: `.select-text.rounded-lg` (v0.5.74+)
|
||||
- Thinking 블록: 조상에 `max-h-[200px]` 클래스 있음 → 필터링
|
||||
|
||||
### 3.2 승인 버튼 (Observer가 접근 가능)
|
||||
- "Always run" 버튼: `<button>` 태그, chat panel 내
|
||||
- 명령어 텍스트: `footer`의 **형제(sibling)** 요소의 `pre.font-mono` (조상 아님!)
|
||||
- 조상 탐색 trail: `d0:button → d1:div → d2:footer` (footer.parentElement === null)
|
||||
- "Retry" 버튼: `<button>` 태그, chat panel 내
|
||||
### 3.2 승인 버튼 — "Always run" (v0.5.93 BTN-DOM-DUMP 확인)
|
||||
|
||||
### 3.3 Diff Review (Accept all / Reject all) — Observer 접근 **불가**
|
||||
- "Accept all" / "Reject all" 버튼은 **에디터 webview**에 렌더링
|
||||
- Observer의 `document`에서 `querySelectorAll('button')` 등으로 찾을 수 없음
|
||||
- ACCEPT-SCAN 디버그 로그로 확인: `button, a, div, span, [role="button"]` 전체 스캔해도 미발견
|
||||
- **해결 경로**: Extension host에서 `antigravity.acceptAgentStep` 명령 실행 (CMD-DISCOVERY에서 발견됨)
|
||||
실제 DOM 구조 (v0.5.92 로그로 확인):
|
||||
- d0: button.flex.cursor-pointer (Always run 버튼)
|
||||
- d1: div.min-w-0
|
||||
- d2: div.flex.items-center.justify-between.rounded-b.border-t (버튼 바)
|
||||
- d3: div (이름 없는 컨테이너)
|
||||
- div.mb-1 = "Running command" (헤더)
|
||||
- div.flex = "❯ gravity_control > ..." (실제 명령어, plain div!)
|
||||
- div.flex = "Always run Cancel" (버튼들)
|
||||
|
||||
> 명령어는 pre.font-mono나 code가 아닌 plain div.flex에 있음.
|
||||
> v30: "Running command" div의 형제를 탐색하여 프롬프트 마커 뒤의 명령어 추출.
|
||||
|
||||
- "Retry" 버튼: button 태그, chat panel 내
|
||||
|
||||
### 3.3 Diff Review (Accept all / Reject all) — Observer 접근 가능 (v0.5.101+)
|
||||
- **v0.5.101 이전**: 에디터 webview에 렌더링, Observer document에서 접근 불가
|
||||
- **v0.5.101 이후**: AG UI 업데이트로 chat panel footer에 `<span class="cursor-pointer">` 태그로 렌더링
|
||||
- Observer의 `allBtns` 선택자에 `span.cursor-pointer` 포함 필수
|
||||
- `matchedType = 'diff_review'`로 분류됨 (L1120: `txt.includes('Accept')`)
|
||||
- auto-approve response 파일에 `_from_ws: true` 마커 필수 (processResponseFile race condition 방지)
|
||||
|
||||
### 3.4 DOM 렌더링 타이밍
|
||||
- "Always run" 버튼이 DOM에 나타난 후, `pre.font-mono` (명령어 텍스트)는 **100~300ms 후** 렌더링
|
||||
- 해결: DEFERRED 컨텍스트 — 500ms 딜레이 후 재추출 (v0.5.84+)
|
||||
- "Always run" 버튼이 DOM에 나타날 때 명령어 div도 함께 렌더링됨
|
||||
- v30의 "Running command" div 탐색은 즉시 성공
|
||||
|
||||
### 3.5 log() relay 필터 규칙
|
||||
Observer의 log() 함수는 키워드 필터로 일부 로그만 extension.log에 relay.
|
||||
새 로그 키워드 추가 시 반드시 필터도 함께 수정해야 함.
|
||||
|
||||
현재 필터 키워드 (v0.5.92+):
|
||||
CV-CLASSES, CV-CHILDREN, child[, CV found, Conversation view,
|
||||
BEACON, ERROR, chat relay, user-cls,
|
||||
CONTEXT, BTN-DOM, DEFERRED, DETECTED
|
||||
|
||||
---
|
||||
|
||||
|
||||
26
docs/devlog/2026-04-19.md
Normal file
26
docs/devlog/2026-04-19.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Devlog 2026-04-19
|
||||
|
||||
## 작업 인덱스
|
||||
|
||||
| # | 시간 | 작업 | 커밋 | 상태 |
|
||||
|---|------|------|------|------|
|
||||
| 001 | 21:22 | v30-32 Observer 명령어 추출 안정화 (터미널 프롬프트 조기감지) | `bd5a7ca` | ✅ |
|
||||
| 002 | 23:16 | v33 Accept all 자동승인 — diff review auto-approve | `6aea48e` | ✅ |
|
||||
| 003 | 00:18 | v34 Accept all 이중 보장 — agentAcceptAllInFile 직접 호출 | `cf1352e` | ✅ |
|
||||
| 004 | 00:34 | v35 code_edit 자동 Accept — step-probe 경로 | `2bf1eb4` | ✅ |
|
||||
| 005 | 04:26 | v36 Accept all span 감지 — 근본 원인 발견 (button→span) | `e95e779` | ✅ |
|
||||
| 006 | 04:34 | v37 openReviewChanges 선호출 — agentAcceptAllInFile 보조 | `3cc3442` | ✅ |
|
||||
| 007 | 04:43 | v38 _from_ws 마커 추가 — Observer polling 실패 근본 수정 | `7c8891b` | ✅ |
|
||||
|
||||
## v0.5.103 — Accept all (Diff Review) 자동 승인 복구
|
||||
|
||||
### 근본 원인 (2가지)
|
||||
1. **Observer 감지 실패**: AG UI가 "Accept all"을 `<button>`이 아닌 `<span class="cursor-pointer">`로 렌더링. Observer의 `allBtns` 선택자가 `button`만 스캔하여 미감지.
|
||||
2. **Response 파일 race condition**: auto-approve response 파일에 `_from_ws: true` 마커 없음 → `processResponseFile`이 Observer보다 먼저 파일 삭제 → Observer polling 무한 실패.
|
||||
|
||||
### 검증 결과
|
||||
- Observer ACCEPT-SCAN: `tag=SPAN cls=cursor-pointer txt=Accept all` ✅
|
||||
- `DETECTED diff_review: Accept all` ✅
|
||||
- `response served to renderer: ...approved=true` (이전 0건 → 7건) ✅
|
||||
- Discord "자동 승인됨 Accept all" 표시 ✅
|
||||
- 화면에서 "Accept all" 버튼 자동 소멸 확인 ✅
|
||||
4
extension/package-lock.json
generated
4
extension/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "gravity-bridge",
|
||||
"version": "0.5.93",
|
||||
"version": "0.5.103",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gravity-bridge",
|
||||
"version": "0.5.93",
|
||||
"version": "0.5.103",
|
||||
"dependencies": {
|
||||
"cheerio": "^1.2.0",
|
||||
"ws": "^8.19.0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "gravity-bridge",
|
||||
"displayName": "Gravity Bridge",
|
||||
"description": "Discord-based unified approval system for Antigravity AI interactions.",
|
||||
"version": "0.5.93",
|
||||
"version": "0.5.103",
|
||||
"publisher": "variet",
|
||||
"engines": {
|
||||
"vscode": "^1.100.0"
|
||||
|
||||
22
extension/scratch/test_accept.js
Normal file
22
extension/scratch/test_accept.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Test: call agentAcceptAllInFile via extension's HTTP bridge trigger-click
|
||||
// This simulates what the approval handler does
|
||||
const http = require('http');
|
||||
|
||||
const PORT = 34332; // from observer setup log
|
||||
|
||||
// Write a trigger-click file to make Observer click "Accept all"
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const bridgePath = path.join(process.env.USERPROFILE, '.gemini', 'antigravity', 'bridge');
|
||||
|
||||
// Check if there's a trigger_click.json
|
||||
const triggerFile = path.join(bridgePath, 'trigger_click.json');
|
||||
console.log('Writing trigger_click.json for accept...');
|
||||
fs.writeFileSync(triggerFile, JSON.stringify({ action: 'approve', type: 'diff_review', ts: Date.now() }), 'utf-8');
|
||||
console.log('Done. Check if Accept all was clicked.');
|
||||
|
||||
// Also check extension log for recent entries
|
||||
const logFile = path.join(bridgePath, 'extension.log');
|
||||
const lines = fs.readFileSync(logFile, 'utf-8').split('\n');
|
||||
const recent = lines.slice(-5);
|
||||
recent.forEach(l => console.log(l.substring(0, 200)));
|
||||
114
extension/scratch/verify_final.js
Normal file
114
extension/scratch/verify_final.js
Normal file
@@ -0,0 +1,114 @@
|
||||
// Final simulation: exact v0.5.96 flow with realistic DOM
|
||||
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||
let s = generateApprovalObserverScript(18080);
|
||||
try { new Function(s); console.log('SYNTAX: OK'); } catch(e) { console.log('SYNTAX ERROR:', e.message); process.exit(1); }
|
||||
|
||||
let promptRe = /[\u003e\u00bb\u276f]\s+(.+)/;
|
||||
let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/;
|
||||
let iconFilterRe = /^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/;
|
||||
|
||||
// Realistic scenario: "Running command" div has siblings including a copy button
|
||||
// The actual DOM probably has a structure like:
|
||||
// div "Running command"
|
||||
// span/div with the copy icon (textContent = "> content_copy" or just "content_copy")
|
||||
// div with the actual prompt+command
|
||||
// div with the buttons
|
||||
|
||||
function v31_simulate(name, siblings) {
|
||||
console.log('\n=== ' + name + ' ===');
|
||||
|
||||
// Step 1: Find "Running command" header
|
||||
let rcIdx = -1;
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
let t = siblings[i].trim();
|
||||
if (t === 'Running command' || (t.indexOf('Running command') !== -1 && t.length < 30)) {
|
||||
rcIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rcIdx < 0) { console.log(' NO RC HEADER'); return; }
|
||||
|
||||
// Step 2: Collect candidates (filter icons and buttons)
|
||||
let cands = [];
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
if (i === rcIdx) continue;
|
||||
let t = siblings[i].trim();
|
||||
if (t.length < 5) continue;
|
||||
if (iconFilterRe.test(t)) { console.log(' FILTER icon: "' + t.substring(0,40) + '"'); continue; }
|
||||
if (/^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i.test(t)) { console.log(' FILTER btn: "' + t.substring(0,40) + '"'); continue; }
|
||||
if (t.indexOf('Always run') !== -1 && t.indexOf('Cancel') !== -1) { console.log(' FILTER btn-bar: "' + t.substring(0,40) + '"'); continue; }
|
||||
cands.push(t);
|
||||
console.log(' CANDIDATE: "' + t.substring(0,60) + '" (len=' + t.length + ')');
|
||||
}
|
||||
|
||||
// Step 3: Sort by length (longest first)
|
||||
cands.sort((a,b) => b.length - a.length);
|
||||
|
||||
// Step 4: Extract command from best candidate
|
||||
for (let cand of cands) {
|
||||
let m = promptRe.exec(cand);
|
||||
if (m && m[1].trim().length > 3) {
|
||||
let cmdV = m[1].trim().replace(stripRe, '').trim();
|
||||
if (cmdV.length < 3) { console.log(' SKIP (too short after strip): "' + cmdV + '"'); continue; }
|
||||
if (/^(Always|Run|Allow|Cancel|Deny)/i.test(cmdV)) continue;
|
||||
console.log(' EXTRACTED: "' + cmdV + '"');
|
||||
return;
|
||||
}
|
||||
if (cand.length > 10 && /[\u276f\u003e]/.test(cand)) {
|
||||
let raw = cand.replace(stripRe, '').trim();
|
||||
console.log(' EXTRACTED (raw): "' + raw + '"');
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log(' NO MATCH - will fallback to "Always run"');
|
||||
}
|
||||
|
||||
// Scenario A: What ACTUALLY happened (3 siblings, "content_copy" mixed in command text)
|
||||
v31_simulate('A: Icon in command text', [
|
||||
'Running command',
|
||||
'\u276f gravity_control > Start-Sleep 12; $logFile content_copy',
|
||||
'Always run keyboard_arrow_up Cancel'
|
||||
]);
|
||||
|
||||
// Scenario B: Copy button as separate small div
|
||||
v31_simulate('B: Icon as separate div + command div', [
|
||||
'Running command',
|
||||
'> content_copy',
|
||||
'\u276f gravity_control > npm run compile',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
// Scenario C: Just "content_copy" standalone (no >)
|
||||
v31_simulate('C: Standalone icon + command', [
|
||||
'Running command',
|
||||
'content_copy',
|
||||
'\u276f gravity_control > git push origin main',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
// Scenario D: Multiple icons mixed
|
||||
v31_simulate('D: Multiple icons + command', [
|
||||
'Running command',
|
||||
'play_arrow',
|
||||
'> content_copy',
|
||||
'\u276f gravity_control > node -e "console.log(1)" content_copy',
|
||||
'Always run keyboard_arrow_up Cancel'
|
||||
]);
|
||||
|
||||
// Scenario E: Edge - no command, only prompt
|
||||
v31_simulate('E: Prompt only', [
|
||||
'Running command',
|
||||
'\u276f gravity_control > ',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
// Scenario F: The v0.5.95 cmdV=content_copy case
|
||||
// This implies regex matched "content_copy" from a "> content_copy" sibling
|
||||
// and there was no longer sibling
|
||||
v31_simulate('F: Only icon sibling (worst case)', [
|
||||
'Running command',
|
||||
'> content_copy',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
console.log('\n=== SIMULATION COMPLETE ===');
|
||||
43
extension/scratch/verify_junk.js
Normal file
43
extension/scratch/verify_junk.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||
let s = generateApprovalObserverScript(18080);
|
||||
|
||||
// Extract the regex strings
|
||||
let junkMatch = s.match(/JUNK_CODE_RE\s*=\s*(\/[^;]+)/);
|
||||
let promptMatch = s.match(/PROMPT_ONLY_RE\s*=\s*(\/[^;]+)/);
|
||||
|
||||
console.log('JUNK_CODE_RE:', junkMatch[1].substring(0, 100));
|
||||
console.log('PROMPT_ONLY_RE:', promptMatch[1]);
|
||||
|
||||
// Use eval to construct the actual regexes
|
||||
let JUNK = eval(junkMatch[1]);
|
||||
let PROMPT = eval(promptMatch[1]);
|
||||
|
||||
let tests = [
|
||||
['\u276f gravity_control > ', 'prompt only (no command)'],
|
||||
['\u276f extension > ', 'prompt only (extension)'],
|
||||
['\u276f gravity_control > $logFile = Join-Path $env:USERPROFILE', 'PS var assignment'],
|
||||
['\u276f extension > npm.cmd run compile', 'npm compile'],
|
||||
['\u276f gravity_control > Start-Sleep 12', 'Start-Sleep'],
|
||||
['\u276f gravity_control > git add -A; git commit -m "test"', 'git commit'],
|
||||
['\u276f gravity_control > node -e "const {gen}=require()"', 'node with require'],
|
||||
['\u276f gravity_control > Get-Content file.txt', 'Get-Content'],
|
||||
['\u276f gravity_control > npm.cmd version patch', 'npm version'],
|
||||
['function test() { return 1; }', 'JS function (should be JUNK)'],
|
||||
['const x = require("fs")', 'JS const (should be JUNK)'],
|
||||
['import { foo } from "bar"', 'JS import (should be JUNK)'],
|
||||
];
|
||||
|
||||
console.log('\n=== CODE ELEMENT FILTER ANALYSIS ===');
|
||||
for (let [text, desc] of tests) {
|
||||
let isJunk = JUNK.test(text);
|
||||
let isPrompt = PROMPT.test(text.trim());
|
||||
let junkPart = isJunk ? text.match(JUNK)[0] : null;
|
||||
|
||||
let status;
|
||||
if (isPrompt) status = 'SKIP-PROMPT';
|
||||
else if (isJunk) status = 'SKIP-JUNK (' + junkPart + ')';
|
||||
else status = 'PASS';
|
||||
|
||||
let isBug = (isJunk || isPrompt) && text.indexOf('\u276f') !== -1 && text.trim().length > 25;
|
||||
console.log((isBug ? 'BUG ' : ' ') + status.padEnd(40) + ' | ' + desc);
|
||||
}
|
||||
42
extension/scratch/verify_regex.js
Normal file
42
extension/scratch/verify_regex.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||
let s = generateApprovalObserverScript(18080);
|
||||
|
||||
// Find the regex used in v30 candidate matching
|
||||
let idx = s.indexOf('candT.match(');
|
||||
if (idx < 0) idx = s.indexOf('sibT.match(');
|
||||
let reStr = s.substring(idx, s.indexOf(');', idx) + 1);
|
||||
console.log('Match code:', reStr.substring(0, 60));
|
||||
|
||||
// Extract just the regex part
|
||||
let reMatch = reStr.match(/\/(.*?)\//);
|
||||
let reSource = reMatch ? reMatch[0] : 'NOT FOUND';
|
||||
console.log('Regex source:', reSource);
|
||||
|
||||
// Build and test the actual regex
|
||||
let re = new RegExp(reMatch[1]);
|
||||
console.log('Regex object:', re);
|
||||
|
||||
// Test with the EXACT patterns from logs
|
||||
let tests = [
|
||||
['Normal', '\u276f gravity_control > Start-Sleep 12 content_copy'],
|
||||
['Git cmd', '\u276f gravity_control > git add -A; git commit -m "test"'],
|
||||
['Short', '> content_copy'],
|
||||
['Prompt only', '\u276f gravity_control > '],
|
||||
['Dir cmd', '\u276f gravity_control > dir content_copy'],
|
||||
];
|
||||
|
||||
console.log('\n=== REGEX TESTS ===');
|
||||
let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/;
|
||||
for (let [name, text] of tests) {
|
||||
let m = re.exec(text);
|
||||
if (m) {
|
||||
let raw = m[1].trim();
|
||||
let cleaned = raw.replace(stripRe, '').trim();
|
||||
console.log(name + ':');
|
||||
console.log(' raw match[1]: "' + raw + '"');
|
||||
console.log(' after strip: "' + cleaned + '"');
|
||||
console.log(' length ok: ' + (cleaned.length >= 3));
|
||||
} else {
|
||||
console.log(name + ': NO MATCH');
|
||||
}
|
||||
}
|
||||
122
extension/scratch/verify_v096.js
Normal file
122
extension/scratch/verify_v096.js
Normal file
@@ -0,0 +1,122 @@
|
||||
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||
let s = generateApprovalObserverScript(18080);
|
||||
|
||||
// 1. SYNTAX CHECK
|
||||
try { new Function(s); console.log('[1] SYNTAX: OK'); } catch(e) { console.log('[1] SYNTAX ERROR:', e.message); process.exit(1); }
|
||||
|
||||
// 2. v30 block exists
|
||||
let v30Start = s.indexOf('// v30:');
|
||||
let v30End = s.indexOf('// v23:', v30Start);
|
||||
console.log('[2] v30 block:', v30Start > 0 && v30End > v30Start ? 'OK' : 'MISSING');
|
||||
|
||||
// 3. Key features present
|
||||
console.log('[3] rcCands:', s.indexOf('rcCands') > 0 ? 'OK' : 'MISSING');
|
||||
console.log('[4] content_copy filter:', s.indexOf('content_copy|content_paste') > 0 ? 'OK' : 'MISSING');
|
||||
console.log('[5] sort by length:', s.indexOf('.sort(') > 0 ? 'OK' : 'MISSING');
|
||||
console.log('[6] icon strip replace:', (s.match(/content_copy/g)||[]).length >= 2 ? 'OK (filter+strip)' : 'CHECK');
|
||||
|
||||
// 4. Simulate exact DOM from BTN-DOM-DUMP + CONTEXT logs
|
||||
let promptRe = /[\u003e\u00bb\u276f]\s+(.+)/;
|
||||
let stripRe = /\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\s*$/;
|
||||
let iconFilterRe = /^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/;
|
||||
let btnFilterRe = /^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i;
|
||||
|
||||
function simulate(name, siblings) {
|
||||
console.log('\n=== ' + name + ' ===');
|
||||
let rcFound = false;
|
||||
for (let sib of siblings) {
|
||||
if (sib === 'Running command' || (sib.indexOf('Running command') !== -1 && sib.length < 30)) {
|
||||
rcFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!rcFound) { console.log(' RC header NOT FOUND'); return null; }
|
||||
|
||||
let cands = [];
|
||||
for (let sib of siblings) {
|
||||
if (sib === 'Running command') continue;
|
||||
if (sib.length < 5) continue;
|
||||
if (iconFilterRe.test(sib)) { console.log(' SKIP icon: "' + sib.substring(0,30) + '"'); continue; }
|
||||
if (btnFilterRe.test(sib)) { console.log(' SKIP btn: "' + sib.substring(0,30) + '"'); continue; }
|
||||
if (sib.indexOf('Always run') !== -1 && sib.indexOf('Cancel') !== -1) { console.log(' SKIP btn-bar: "' + sib.substring(0,30) + '"'); continue; }
|
||||
cands.push(sib);
|
||||
}
|
||||
cands.sort((a,b) => b.length - a.length);
|
||||
console.log(' Candidates: ' + cands.length);
|
||||
for (let i = 0; i < cands.length; i++) {
|
||||
console.log(' [' + i + '] len=' + cands[i].length + ': "' + cands[i].substring(0,80) + '"');
|
||||
}
|
||||
|
||||
for (let cand of cands) {
|
||||
let m = promptRe.exec(cand);
|
||||
if (m && m[1].trim().length > 3) {
|
||||
let cmdV = m[1].trim().replace(stripRe, '').trim();
|
||||
if (cmdV.length < 3) continue;
|
||||
console.log(' RESULT: "' + cmdV + '"');
|
||||
return cmdV;
|
||||
}
|
||||
if (cand.length > 10 && /[\u276f\u003e]/.test(cand)) {
|
||||
let raw = cand.replace(stripRe, '').trim();
|
||||
console.log(' RESULT (raw): "' + raw + '"');
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
console.log(' RESULT: NO MATCH');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Case 1: From BTN-DOM-DUMP (3 siblings, command + content_copy icon)
|
||||
simulate('Case1: Normal command with icon', [
|
||||
'Running command',
|
||||
'\u276f gravity_control > Start-Sleep 12; $logFile content_copy',
|
||||
'Always run keyboard_arrow_up Cancel'
|
||||
]);
|
||||
|
||||
// Case 2: content_copy as standalone sibling
|
||||
simulate('Case2: Icon as separate div', [
|
||||
'Running command',
|
||||
'content_copy',
|
||||
'\u276f gravity_control > npm run compile',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
// Case 3: No icon appended
|
||||
simulate('Case3: Clean command', [
|
||||
'Running command',
|
||||
'\u276f gravity_control > git add -A; git commit -m "test"',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
// Case 4: Very long command
|
||||
simulate('Case4: Long command', [
|
||||
'Running command',
|
||||
'\u276f gravity_control > Select-String -Path "$env:USERPROFILE\\.gemini\\antigravity\\bridge\\extension.log" -Pattern "CONTEXT" content_copy',
|
||||
'Always run keyboard_arrow_up Cancel'
|
||||
]);
|
||||
|
||||
// Case 5: Prompt only (no command yet)
|
||||
simulate('Case5: Prompt only', [
|
||||
'Running command',
|
||||
'\u276f gravity_control > ',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
// Case 6: Multiple icon texts
|
||||
simulate('Case6: Multiple icons', [
|
||||
'Running command',
|
||||
'play_arrow',
|
||||
'content_copy',
|
||||
'\u276f gravity_control > dir content_copy',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
// Case 7: Observed log pattern - "content_copy" was cmdV
|
||||
// This means the regex matched on just "content_copy" with a > before it
|
||||
// Possible: the sibling text is "> content_copy" (very short prompt)
|
||||
simulate('Case7: Short prompt with icon only', [
|
||||
'Running command',
|
||||
'> content_copy',
|
||||
'Always run Cancel'
|
||||
]);
|
||||
|
||||
console.log('\n=== ALL TESTS COMPLETE ===');
|
||||
52
extension/scratch/verify_v32.js
Normal file
52
extension/scratch/verify_v32.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const {generateApprovalObserverScript} = require('../out/observer-script');
|
||||
let s = generateApprovalObserverScript(18080);
|
||||
|
||||
// Extract the terminal prompt regex from generated code
|
||||
let idx = s.indexOf('_termPromptMatch');
|
||||
let reCtx = s.substring(idx, s.indexOf(');', idx) + 1);
|
||||
console.log('v32 code:', reCtx.substring(0, 80));
|
||||
|
||||
// Extract regex
|
||||
let reMatch = reCtx.match(/\/(.+?)\//);
|
||||
let termRe = new RegExp(reMatch[1]);
|
||||
console.log('v32 regex:', termRe);
|
||||
|
||||
let stripRe = /\s*(content_copy|content_paste|play_arrow)\s*$/;
|
||||
|
||||
let tests = [
|
||||
// Should MATCH (terminal commands)
|
||||
['\u276f gravity_control > Start-Sleep 12', true, 'Start-Sleep'],
|
||||
['\u276f gravity_control > npm.cmd run compile', true, 'npm compile'],
|
||||
['\u276f gravity_control > $logFile = Join-Path $env:USERPROFILE', true, 'PS variable (had JUNK match)'],
|
||||
['\u276f gravity_control > git add -A; git commit -m "test"', true, 'git commit'],
|
||||
['\u276f gravity_control > node -e "const {gen}=require(\'./out\')"', true, 'node with const (was JUNK)'],
|
||||
['\u276f extension > npm.cmd run compile', true, 'extension npm'],
|
||||
['\u276f gravity_control > Start-Sleep 12 content_copy', true, 'with icon (strip)'],
|
||||
['\u276f gravity_control > Get-Content f.txt | Select-Object -Last 5', true, 'Get-Content'],
|
||||
// Should NOT match (prompt only, no command)
|
||||
['\u276f gravity_control > ', false, 'prompt only'],
|
||||
['\u276f extension > ', false, 'prompt only ext'],
|
||||
// Should NOT match (not terminal - JS code)
|
||||
['function test() { return 1; }', false, 'JS function'],
|
||||
['const x = require("fs")', false, 'JS const'],
|
||||
['import { foo } from "bar"', false, 'JS import'],
|
||||
// Should NOT match (no prompt marker)
|
||||
['gravity_control > dir', false, 'no ❯ marker'],
|
||||
];
|
||||
|
||||
console.log('\n=== v32 TERMINAL PROMPT REGEX TESTS ===');
|
||||
let pass = 0, fail = 0;
|
||||
for (let [text, shouldMatch, desc] of tests) {
|
||||
let m = termRe.exec(text);
|
||||
let matched = false;
|
||||
let cmdV = null;
|
||||
if (m && m[1] && m[1].trim().length > 2) {
|
||||
cmdV = m[1].trim().replace(stripRe, '').trim();
|
||||
matched = cmdV.length > 2;
|
||||
}
|
||||
let ok = matched === shouldMatch;
|
||||
if (ok) pass++; else fail++;
|
||||
console.log((ok ? 'PASS' : 'FAIL') + ' | ' + (matched ? 'MATCH' : 'SKIP ').padEnd(5) + ' | ' + desc);
|
||||
if (matched && cmdV) console.log(' cmd: "' + cmdV + '"');
|
||||
}
|
||||
console.log('\nResult: ' + pass + '/' + (pass+fail) + ' passed' + (fail > 0 ? ' (' + fail + ' FAILED!)' : ' ALL OK'));
|
||||
@@ -285,11 +285,13 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
|
||||
// v19: "Always run" auto-approve — MUST run BEFORE any filter can reject it
|
||||
// Detects from rawCmd OR from buttons array (Observer may detect sibling first)
|
||||
let alwaysRunDetected = /^Always\s+run$/i.test(rawCmd);
|
||||
// v33: Also auto-approve "Accept all" (diff review) and "Accept" buttons
|
||||
const AUTO_APPROVE_RE = /^(Always\s+run|Accept\s+all|Accept)$/i;
|
||||
let alwaysRunDetected = AUTO_APPROVE_RE.test(rawCmd);
|
||||
let alwaysRunBtnIndex = alwaysRunDetected ? 0 : -1;
|
||||
if (!alwaysRunDetected && Array.isArray(data.buttons)) {
|
||||
for (let bi = 0; bi < data.buttons.length; bi++) {
|
||||
if (/^Always\s+run$/i.test((data.buttons[bi].text || '').trim())) {
|
||||
if (AUTO_APPROVE_RE.test((data.buttons[bi].text || '').trim())) {
|
||||
alwaysRunDetected = true;
|
||||
alwaysRunBtnIndex = bi;
|
||||
break;
|
||||
@@ -297,6 +299,27 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
}
|
||||
}
|
||||
if (alwaysRunDetected) {
|
||||
// v34: If this is "Accept all" / "Accept", also call agentAcceptAllInFile directly
|
||||
const isAcceptAll = /^Accept/i.test(rawCmd) || (data.step_type === 'diff_review');
|
||||
if (isAcceptAll) {
|
||||
ctx.logToFile(`[HTTP] AUTO-APPROVE Accept all → opening review + agentAcceptAllInFile`);
|
||||
try {
|
||||
const vscode = require('vscode');
|
||||
// v37: Must focus diff review panel BEFORE calling agentAcceptAllInFile
|
||||
// Without this, the command succeeds but has no effect
|
||||
(async () => {
|
||||
try {
|
||||
await vscode.commands.executeCommand('antigravity.openReviewChanges');
|
||||
ctx.logToFile(`[HTTP] openReviewChanges OK`);
|
||||
await new Promise((r: any) => setTimeout(r, 500));
|
||||
await vscode.commands.executeCommand('antigravity.prioritized.agentAcceptAllInFile');
|
||||
ctx.logToFile(`[HTTP] ✅ agentAcceptAllInFile SUCCESS`);
|
||||
} catch (e: any) {
|
||||
ctx.logToFile(`[HTTP] ❌ agentAcceptAllInFile: ${e.message?.substring(0, 100)}`);
|
||||
}
|
||||
})();
|
||||
} catch (e: any) { ctx.logToFile(`[HTTP] ❌ vscode require failed: ${e.message}`); }
|
||||
}
|
||||
// Try enrichment for better Discord display text
|
||||
let displayCmd = rawCmd;
|
||||
ctx.logToFile(`[HTTP] AUTO-APPROVE raw: cmd="${rawCmd}" desc="${rawDesc.substring(0, 120)}" buttons=${JSON.stringify((data.buttons || []).map((b: any) => b.text)).substring(0, 200)}`);
|
||||
@@ -356,6 +379,8 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
button_index: alwaysRunBtnIndex >= 0 ? alwaysRunBtnIndex : 0,
|
||||
step_type: data.step_type || 'command',
|
||||
project_name: ctx.projectName,
|
||||
_from_ws: true, // v38: prevent processResponseFile from consuming before Observer polls
|
||||
_auto_approve_ttl: Date.now() + 60_000, // auto-expire after 60s
|
||||
};
|
||||
fs.writeFileSync(
|
||||
path.join(responseDir, `${rid}.json`),
|
||||
|
||||
@@ -12,7 +12,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
function log(m){
|
||||
console.log('[GB Observer] '+m);
|
||||
// v19: Relay important logs to extension via HTTP so they appear in extension.log
|
||||
if (BASE && (m.indexOf('CV-CLASSES')!==-1 || m.indexOf('CV-CHILDREN')!==-1 || m.indexOf('child[')!==-1 || m.indexOf('CV found')!==-1 || m.indexOf('Conversation view')!==-1 || m.indexOf('BEACON')!==-1 || m.indexOf('ERROR')!==-1 || m.indexOf('chat relay')!==-1 || m.indexOf('user-cls')!==-1 || m.indexOf('CONTEXT')!==-1 || m.indexOf('BTN-DOM')!==-1 || m.indexOf('DEFERRED')!==-1 || m.indexOf('DETECTED')!==-1)) {
|
||||
if (BASE && (m.indexOf('CV-CLASSES')!==-1 || m.indexOf('CV-CHILDREN')!==-1 || m.indexOf('child[')!==-1 || m.indexOf('CV found')!==-1 || m.indexOf('Conversation view')!==-1 || m.indexOf('BEACON')!==-1 || m.indexOf('ERROR')!==-1 || m.indexOf('chat relay')!==-1 || m.indexOf('user-cls')!==-1 || m.indexOf('CONTEXT')!==-1 || m.indexOf('BTN-DOM')!==-1 || m.indexOf('DEFERRED')!==-1 || m.indexOf('DETECTED')!==-1 || m.indexOf('ACCEPT')!==-1)) {
|
||||
try { fetch(BASE+'/log', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({msg:m.substring(0,2000)})}); } catch(e){}
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,20 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
if (!codeText || codeText.length <= 5) continue;
|
||||
if (/^Running\\s*\\d/i.test(codeText)) continue;
|
||||
_sawCodeEls = true;
|
||||
// v32: Terminal prompt detection — extract command BEFORE JUNK/PROMPT filters
|
||||
// PS/bash commands can contain JS keywords (return, function, const) → false JUNK matches
|
||||
var _termPromptMatch = codeText.match(/^[\\u276f\\u00bb]\\s+[^\\n]*[\\u003e]\\s+(.+)/);
|
||||
if (_termPromptMatch && _termPromptMatch[1].trim().length > 2) {
|
||||
var _termCmd = _termPromptMatch[1].trim();
|
||||
_termCmd = _termCmd.replace(/\\s*(content_copy|content_paste|play_arrow)\\s*$/, '').trim();
|
||||
if (_termCmd.length > 2) {
|
||||
_allSkipped = false;
|
||||
_bestCodeText = 'Running command: ' + _termCmd;
|
||||
log('CONTEXT-OK d='+depth+' src=terminal-prompt cmd='+_termCmd.substring(0,80));
|
||||
_lastContextDebug = _debugTrail.join(' ');
|
||||
return _bestCodeText;
|
||||
}
|
||||
}
|
||||
if (PROMPT_ONLY_RE.test(codeText.trim())) {
|
||||
_debugTrail.push('skip_prompt_ci='+ci+':'+codeText.substring(0,30));
|
||||
continue;
|
||||
@@ -191,20 +205,51 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
}
|
||||
// v30: Command text is in plain divs near "Running command" header, not pre/code
|
||||
var rcDivs = node.querySelectorAll('div');
|
||||
// v30 diagnostic: log what we find at each depth where code was skipped
|
||||
if (_sawCodeEls && rcDivs.length > 0 && depth <= 5) {
|
||||
var rcSample = [];
|
||||
for (var rdi = 0; rdi < Math.min(rcDivs.length, 8); rdi++) {
|
||||
var rdt = (rcDivs[rdi].textContent || '').trim().substring(0,40);
|
||||
var rdc = rcDivs[rdi].children ? rcDivs[rdi].children.length : 0;
|
||||
rcSample.push('ch'+rdc+':"'+rdt+'"');
|
||||
}
|
||||
log('CONTEXT-v30-SCAN d='+depth+' divs='+rcDivs.length+' ['+rcSample.join(', ')+']');
|
||||
}
|
||||
for (var rci = 0; rci < rcDivs.length; rci++) {
|
||||
var rcTxt = (rcDivs[rci].textContent || '').trim();
|
||||
if (rcTxt === 'Running command' && rcDivs[rci].parentElement) {
|
||||
var rcP = rcDivs[rci].parentElement;
|
||||
var rcEl = rcDivs[rci];
|
||||
var rcChildCount = rcEl.children ? rcEl.children.length : 0;
|
||||
var rcTxt = (rcEl.textContent || '').trim();
|
||||
if ((rcTxt === 'Running command' || (rcChildCount === 0 && rcTxt.indexOf('Running command') !== -1 && rcTxt.length < 30)) && rcEl.parentElement) {
|
||||
var rcP = rcEl.parentElement;
|
||||
var rcCands = [];
|
||||
for (var rcsi = 0; rcsi < rcP.children.length; rcsi++) {
|
||||
if (rcP.children[rcsi] === rcDivs[rci]) continue;
|
||||
if (rcP.children[rcsi] === rcEl) continue;
|
||||
var sibT = (rcP.children[rcsi].textContent || '').trim();
|
||||
var pM = sibT.match(/[\\u003e\\u00bb\\u276f]\\s+(.+)/);
|
||||
if (sibT.length < 5) continue;
|
||||
if (/^(content_copy|content_paste|play_arrow|check_circle|chevron_|keyboard_arrow|more_horiz|more_vert|expand_|alternate_email|arrow_drop)/.test(sibT)) continue;
|
||||
if (/^(Always|Run|Allow|Cancel|Deny|keyboard_arrow)/i.test(sibT)) continue;
|
||||
if (sibT.indexOf('Always run') !== -1 && sibT.indexOf('Cancel') !== -1) continue;
|
||||
rcCands.push(sibT);
|
||||
}
|
||||
rcCands.sort(function(a,b){ return b.length - a.length; });
|
||||
log('CONTEXT-v30 RC d='+depth+' cands='+rcCands.length+(rcCands.length>0?' best="'+rcCands[0].substring(0,60)+'"':''));
|
||||
for (var rcci = 0; rcci < rcCands.length; rcci++) {
|
||||
var candT = rcCands[rcci];
|
||||
var pM = candT.match(/[\\u003e\\u00bb\\u276f]\\s+(.+)/);
|
||||
if (pM && pM[1].trim().length > 3) {
|
||||
var cmdV = pM[1].trim().substring(0, 300);
|
||||
var cmdV = pM[1].trim();
|
||||
cmdV = cmdV.replace(/\\s*(content_copy|content_paste|play_arrow|check_circle|keyboard_arrow[_a-z]*)\\s*$/, '').trim();
|
||||
if (cmdV.length < 3) continue;
|
||||
if (/^(Always|Run|Allow|Cancel|Deny)/i.test(cmdV)) continue;
|
||||
log('CONTEXT-OK d='+depth+' src=running-cmd trail='+_debugTrail.join(' '));
|
||||
log('CONTEXT-OK d='+depth+' src=running-cmd cmdV='+cmdV.substring(0,80));
|
||||
_lastContextDebug = _debugTrail.join(' ');
|
||||
return 'Running command: ' + cmdV;
|
||||
return 'Running command: ' + cmdV.substring(0, 300);
|
||||
}
|
||||
if (candT.length > 10 && /[\\u276f\\u003e]/.test(candT)) {
|
||||
var rawC = candT.replace(/\\s*(content_copy|content_paste|play_arrow)\\s*$/, '').trim();
|
||||
log('CONTEXT-OK d='+depth+' src=running-cmd-raw rawC='+rawC.substring(0,80));
|
||||
_lastContextDebug = _debugTrail.join(' ');
|
||||
return rawC.substring(0, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1035,7 +1080,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
if(!_ready)return;
|
||||
scanChatBodies();
|
||||
var now=Date.now();
|
||||
var allBtns=document.querySelectorAll('button, [role="button"], a.monaco-button, .monaco-text-button, vscode-button');
|
||||
var allBtns=document.querySelectorAll('button, [role="button"], a.monaco-button, .monaco-text-button, vscode-button, span.cursor-pointer');
|
||||
if(!allBtns.length)return;
|
||||
|
||||
// v25: One-shot debug ??find Accept/Reject elements in ANY tag (run once per 30s)
|
||||
|
||||
@@ -808,6 +808,19 @@ function setupMonitor() {
|
||||
source: 'step_probe_offset',
|
||||
safe_to_auto_run: isSafeToAutoRun,
|
||||
});
|
||||
// v35: Auto-accept code edits (offset path)
|
||||
if (['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(toolName)) {
|
||||
ctx.logToFile(`[STEP-PROBE] v35: code_edit (offset) → auto-accepting in 500ms`);
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const vscode = require('vscode');
|
||||
await vscode.commands.executeCommand('antigravity.prioritized.agentAcceptAllInFile');
|
||||
ctx.logToFile(`[STEP-PROBE] ✅ agentAcceptAllInFile (offset) SUCCESS`);
|
||||
} catch (e: any) {
|
||||
ctx.logToFile(`[STEP-PROBE] ❌ agentAcceptAllInFile (offset): ${e.message?.substring(0, 100)}`);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE: no break — process ALL parallel WAITING steps
|
||||
@@ -861,6 +874,20 @@ function setupMonitor() {
|
||||
source: 'step_probe',
|
||||
safe_to_auto_run: isSafeToAutoRun,
|
||||
});
|
||||
// v35: Auto-accept code edits via agentAcceptAllInFile
|
||||
// Observer can't see "Accept all" button (different DOM layer)
|
||||
if (['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(toolName)) {
|
||||
ctx.logToFile(`[STEP-PROBE] v35: code_edit detected → auto-accepting in 500ms`);
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const vscode = require('vscode');
|
||||
await vscode.commands.executeCommand('antigravity.prioritized.agentAcceptAllInFile');
|
||||
ctx.logToFile(`[STEP-PROBE] ✅ agentAcceptAllInFile SUCCESS`);
|
||||
} catch (e: any) {
|
||||
ctx.logToFile(`[STEP-PROBE] ❌ agentAcceptAllInFile: ${e.message?.substring(0, 100)}`);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE: no break — process ALL parallel WAITING steps
|
||||
|
||||
Reference in New Issue
Block a user