fix(step-probe): ensure fast AI responses and tool calls are captured by real-time block

This commit is contained in:
Variet Worker
2026-04-10 17:12:21 +09:00
parent 300338d5d3
commit 488b36f192
12 changed files with 352 additions and 32 deletions

View File

@@ -229,3 +229,9 @@
- **<2A><><EFBFBD><EFBFBD>**: GetAllCascadeTrajectories<65><73> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> pagination <20>ɼ<EFBFBD><C9BC><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰų<CFB0> <20><><EFBFBD><EFBFBD> 10 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ɸ<EFBFBD>.
- **<2A>ذ<EFBFBD>**: step-probe.ts<74><73><EFBFBD><EFBFBD> <20>⺻ GetAllCascadeTrajectories<65><73> <20><><EFBFBD>Ҿ<EFBFBD> <20><><EFBFBD><EFBFBD> Ʈ<><C6AE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E4B8AE> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD> GetDiagnostics API<50><49> <20><><EFBFBD><EFBFBD> ȣ<><C8A3><EFBFBD>ϰ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> <20>ֽ<EFBFBD> Session ID<49><44> <20><>ġ<EFBFBD><C4A1> <20>ʰ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰ<EFBFBD> <20><>.
- **<2A><><EFBFBD><EFBFBD>**: LS Backend<6E><64><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> RPC<50><43> <20>Ѱ<EFBFBD><D1B0><EFBFBD> Argument <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ȸ<><C8B8><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD>, <20><><EFBFBD><EFBFBD> GetDiagnostics <20><> <20><EFBFBD><E9B5B5> <20><><EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD> Ȱ<><C8B0><EFBFBD><EFBFBD> <20><>.
### [2026-04-10] [Probe Logging] — AI응답 텍스트 & WAITING 스텝 동시 누락 버그
- **증상**: 굉장히 빠른 AI 응답(또는 즉각적인 툴 호출) 시 `step-probe.ts`가 메시지와 승인 다이얼로그를 모두 Discord로 릴레이하지 못함.
- **원인**: 실시간 텍스트 캡처(`delta > 0`) 조건에 `isRunning &&`이 걸려있어, 상태가 `WAITING`이나 `IDLE`로 즉시 넘어가면 텍스트를 캡처하는 루틴이 전부 스킵됨. 또한 이 순간 `isStall` 조건도 타지 않아 `WAITING` 디텍션도 증발함.
- **해결**: 실시간 캡처 로직에서 `isRunning &&` 조건을 제거하고, `delta > 0`일 때 추가된 최신 스텝을 스캔하면서 `PLANNER_RESPONSE`와 `WAITING` 스텝을 모두 처리하도록 수정함.
- **주의**: LS Backend 10개 Session 제한 버그가 있어, 다른 창에서 수동 채팅(`1fbca84c`)이 IDLE로 남아있으면 자동화 에이전트의 워크스페이스 세션과 헷갈릴 수 있으나, 이 버그는 polling 타이밍 문제였음.

View File

@@ -1,11 +1,3 @@
# 2026-04-10 데브로그
## 작업 내역
| NNN | HH:MM | 작업 설명 | 커밋해시 | 완료 방면 |
| NNN | 시간 | 작업 설명 | 커밋해시 | 완료여부 |
|---|---|---|---|---|
| 001 | 15:53 | Gravity Bridge AI 응답 텍스트가 누락되는 버그 픽스 (extractPlannerText 적용 및 Nested 조회 추가) | TBD | ✅ |
| 002 | 16:05 | Gravity Bridge 빠른 응답 누락 오류 해결 (IDLE-to-IDLE 패스 로직 완화) | TBD | ✅ || 003 | 16:12 | [Bridge] DOM Observer <20><>Ž<EFBFBD><C5BD> <20><><EFBFBD><EFBFBD> (PATS <20><>Ȱ<EFBFBD><C8B0>ȭ)<29><> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD> <20>˸<EFBFBD> <20><><EFBFBD><EFBFBD> | TBD | ? |
| 004 | 16:31 | [Bot] chat_snapshot_scanner 미처리 예외 큐 막힘 현상 해결 및 Hermes Gateway 재시작 | TBD | ✅ |
| 005 | 16:50 | [Extension] GetAllCascadeTrajectories 10-Item Limit <20><>ȸ<EFBFBD><C8B8> GetDiagnostics <20><><EFBFBD><EFBFBD> <20><>ȸ <20><>ġ | TBD | ? |
| 001 | 17:11 | step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스 | COMMITTING | ✅ |

View File

@@ -1,23 +1,13 @@
# AI ?띿뒪???묐떟 異붿텧 ?꾨씫 踰꾧렇 ?닿껐 (Nested Payload)
# step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스
- **?쒓컙**: 2026-04-10 15:30~15:53
- **Commit**: TBD
- **Vikunja**: TBD
- **시간**: 2026-04-10 17:11
- **Commit**: `COMMITTING`
- **Vikunja**: #125 → done
## ?몃윭釉붿뒋?? AI???띿뒪?멸? Discord濡??ㅼ? ?딅뒗 臾몄젣
**臾몄젣 ?곹솴:**
- ?붿뒪肄붾뱶 ?뮠 AI ?묐떟 濡쒓렇媛€ ?꾩삁 李랁엳吏€ ?딆쓬
- Auto-approve embed 踰꾧렇 ?섏젙(0.5.21) ?댄썑?먮룄 ?묐떟 蹂몃Ц 遺€??臾몄젣??吏€??
**?먯씤 遺꾩꽍:**
- step-probe.ts??罹≪쿂 猷⑦떞??s?.plannerResponse留?李몄“?섏뿬 modifiedResponse,
awText, ext 3媛€€ ?꾨뱶???섎뱶肄붾뵫 ?섏〈.
- ?섏?留?AG??理쒖떊 RPC???뱀젙 紐⑤뜽?€ s.step.plannerResponse.summary ???ㅼ뼇?섍퀬 ?고쉶?곸씤 depth瑜?諛섑솚?섎?濡? 湲곗〈 ?뚯떛 肄붾뱶媛€ 紐⑤몢 ?ㅽ뙣?섍퀬
ull 泥섎━??
## 결정 사항
- AI 응답이 비정상적으로 빠를 경우 `RUNNING` 상태의 2초 polling 창을 우회하여 `IDLE` / `WAITING`로 진입해버리는 버그가 있었습니다.
- 기존에는 `isRunning && currentCount > ...`로만 Real-time Capture가 동작하여 전부 스킵되는 증상 확인.
- `isRunning` 조건을 삭제하고, `delta > 0`인 경우 `GetCascadeTrajectorySteps`를 페치하여 `PLANNER_RESPONSE``WAITING` 스텝을 동시에 처리하도록 개선했습니다.
**?닿껐 諛⑸쾿:**
- 湲곗〈??遺꾨━?대몦 extractPlannerText ?⑥닔瑜??곴레 ?쒖슜?섎룄濡?step-probe.ts 濡ㅻ갚/?섏젙
- extractPlannerText ?대? 濡쒖쭅??step.step?.plannerResponse???먯깋?섎뒗 濡쒖쭅 異붽?
- Node REPL???듯빐 Flat, Nested 紐⑹뾽 JSON ?뚯떛??紐⑤몢 ?뺤긽 ?섑뻾?⑥쓣 ?뺤씤
## 誘몄셿猷??ы빆
- ?놁쓬
## 미완료
- 없음.

View File

@@ -369,7 +369,7 @@ function setupMonitor() {
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
// Real-time response capture: fetch latest steps on every delta>0
if (isRunning && currentCount > lastResponseCaptureStep && ctx.sdk) {
if (currentCount > lastResponseCaptureStep && ctx.sdk) {
try {
const rtOffset = Math.max(0, currentCount - 3);
const rtResp = await ctx.sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
@@ -408,7 +408,40 @@ function setupMonitor() {
? text.substring(0, 3500) + '\n\n_(이하 생략)_'
: text;
ctx.writeChatSnapshot(`💬 **AI 응답**\n\n${truncated}`);
break;
}
}
if (s?.status === 'CORTEX_STEP_STATUS_WAITING') {
const toolCall = s?.metadata?.toolCall;
const toolName = toolCall?.name || (sType || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase();
const { cmd: command, desc: description, isSafe: isSafeToAutoRun } = formatStepProbeCommand(toolName, actualIdx, sType || '', toolCall);
ctx.logToFile(`[STEP-PROBE] ★ WAITING (RT)! step=${actualIdx} type=${sType} cmd='${command}'`);
if (actualIdx !== ctx.lastPendingStepIndex) {
ctx.stallProbed = true;
if (actualIdx > ctx.lastPendingStepIndex) {
ctx.lastPendingStepIndex = actualIdx;
}
lastPendingTime = Date.now();
ctx.sawRunningAfterPending = false;
if (ctx.projectName !== 'default') {
const toolCat = ['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;
writePendingApproval({
conversation_id: ctx.activeSessionId,
command,
description,
step_type: toolCat,
step_index: actualIdx,
source: 'step_probe_rt',
safe_to_auto_run: isSafeToAutoRun,
});
}
}
}
}

36
scratch_devlog.py Normal file
View File

@@ -0,0 +1,36 @@
import os, datetime
now = datetime.datetime.now()
date_str = now.strftime('%Y-%m-%d')
time_str = now.strftime('%H:%M')
with open(r'c:\Users\Variet-Worker\Desktop\gravity_control\.agents\references\known-issues.md', 'a', encoding='utf-8') as f:
f.write('\n### ['+date_str+'] [Probe Logging] — AI응답 텍스트 & WAITING 스텝 동시 누락 버그\n')
f.write('- **증상**: 굉장히 빠른 AI 응답(또는 즉각적인 툴 호출) 시 `step-probe.ts`가 메시지와 승인 다이얼로그를 모두 Discord로 릴레이하지 못함.\n')
f.write('- **원인**: 실시간 텍스트 캡처(`delta > 0`) 조건에 `isRunning &&`이 걸려있어, 상태가 `WAITING`이나 `IDLE`로 즉시 넘어가면 텍스트를 캡처하는 루틴이 전부 스킵됨. 또한 이 순간 `isStall` 조건도 타지 않아 `WAITING` 디텍션도 증발함.\n')
f.write('- **해결**: 실시간 캡처 로직에서 `isRunning &&` 조건을 제거하고, `delta > 0`일 때 추가된 최신 스텝을 스캔하면서 `PLANNER_RESPONSE`와 `WAITING` 스텝을 모두 처리하도록 수정함.\n')
f.write('- **주의**: LS Backend 10개 Session 제한 버그가 있어, 다른 창에서 수동 채팅(`1fbca84c`)이 IDLE로 남아있으면 자동화 에이전트의 워크스페이스 세션과 헷갈릴 수 있으나, 이 버그는 polling 타이밍 문제였음.\n')
print('known-issues updated.')
log_dir = r'c:\Users\Variet-Worker\Desktop\gravity_control\docs\devlog'
index_file = os.path.join(log_dir, date_str + '.md')
try:
with open(index_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
nnn = len([l for l in lines if '|' in l])
except:
os.makedirs(log_dir, exist_ok=True)
with open(index_file, 'w', encoding='utf-8') as f:
f.write('| NNN | 시간 | 작업 설명 | 커밋해시 | 완료여부 |\n|---|---|---|---|---|\n')
nnn = 1
entry_num = f'{nnn:03d}'
entry_file = os.path.join(log_dir, 'entries', f'{date_str.replace("-", "")}-{entry_num}.md')
with open(index_file, 'a', encoding='utf-8') as f:
f.write(f'| {entry_num} | {time_str} | step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스 | COMMITTING | ✅ |\n')
os.makedirs(os.path.dirname(entry_file), exist_ok=True)
with open(entry_file, 'w', encoding='utf-8') as f:
f.write(f'# step-probe.ts 의 isRunning 조건 누락으로 인한 릴레이 증발 버그 픽스\n\n- **시간**: {date_str} {time_str}\n- **Commit**: `COMMITTING`\n- **Vikunja**: #125 → done\n\n## 결정 사항\n- AI 응답이 비정상적으로 빠를 경우 `RUNNING` 상태의 2초 polling 창을 우회하여 `IDLE` / `WAITING`로 진입해버리는 버그가 있었습니다.\n- 기존에는 `isRunning && currentCount > ...`로만 Real-time Capture가 동작하여 전부 스킵되는 증상 확인.\n- `isRunning` 조건을 삭제하고, `delta > 0`인 경우 `GetCascadeTrajectorySteps`를 페치하여 `PLANNER_RESPONSE`와 `WAITING` 스텝을 동시에 처리하도록 개선했습니다.\n\n## 미완료\n- 없음.\n')

38
scratch_diag.py Normal file
View File

@@ -0,0 +1,38 @@
import urllib.request
import json
import ssl
url = "https://127.0.0.1:54285/exa.language_server_pb.LanguageServerService/GetDiagnostics"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
req = urllib.request.Request(url, data=json.dumps({}).encode(), headers={
'Content-Type': 'application/json',
'token': '5e529def-51fe-4bde-9955-5eca7299bd89'
})
try:
with urllib.request.urlopen(req, context=ctx) as response:
res = json.loads(response.read().decode('utf-8'))
recent = res.get('recentTrajectories', [])
print(f"HTTPS Total: {len(recent)}")
for r in recent:
print(r.get('googleAgentId'), r.get('lastModifiedTime'), r.get('status'))
except Exception as e:
print(f"HTTPS failed: {e}")
url = url.replace('https', 'http')
req = urllib.request.Request(url, data=json.dumps({}).encode(), headers={
'Content-Type': 'application/json',
'token': '5e529def-51fe-4bde-9955-5eca7299bd89'
})
try:
with urllib.request.urlopen(req) as response:
res = json.loads(response.read().decode('utf-8'))
recent = res.get('recentTrajectories', [])
print(f"HTTP Total: {len(recent)}")
for r in recent:
print(r.get('googleAgentId'), r.get('lastModifiedTime'), r.get('status'))
except Exception as e2:
print(f"HTTP failed: {e2}")

36
scratch_diag2.py Normal file
View File

@@ -0,0 +1,36 @@
import urllib.request
import json
def fetch_ls(port, csrf, method, args):
url = f"http://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/{method}"
req = urllib.request.Request(url, data=json.dumps(args).encode(), headers={
'Content-Type': 'application/json',
'x-antigravity-csrf-token': csrf
})
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except Exception as e:
return f"Error: {e}"
print("Connecting to LS port 60517 (global)...")
csrf_global = "7c0c7815-ec11-48d6-9866-daab2690448f"
port_global = 60517
print("\n--- GetAllCascadeTrajectories on 60517 ---")
res = fetch_ls(port_global, csrf_global, "GetAllCascadeTrajectories", {"limit": 100, "descending": True})
if isinstance(res, dict) and 'trajectorySummaries' in res:
keys = list(res['trajectorySummaries'].keys())
print(f"Total entries: {len(keys)}")
for k in keys[:5]: print(f" - {k}")
if "370d1a09-1fa8-4aed-90d7-4024e36b3a2d" in keys:
print("YES! 370d1a09 found on 60517!")
else:
print(res)
print("\n--- GetCascadeTrajectory on 60517 for 370d1a09 ---")
res2 = fetch_ls(port_global, csrf_global, "GetCascadeTrajectory", {"googleAgentId": "370d1a09-1fa8-4aed-90d7-4024e36b3a2d"})
if isinstance(res2, dict) and 'trajectory' in res2:
print("Found trajectory!")
else:
print(res2)

38
scratch_diag4.py Normal file
View File

@@ -0,0 +1,38 @@
import urllib.request
import json
import ssl
def fetch_ls(port, csrf, method, args):
url = f"http://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/{method}"
for hs in ['x-antigravity-csrf-token', 'token']:
req = urllib.request.Request(url, data=json.dumps(args).encode(), headers={
'Content-Type': 'application/json',
hs: csrf
})
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except Exception as e:
continue
return "Failed"
# Process 1
p1 = 60517
c1 = "7c0c7815-ec11-48d6-9866-daab2690448f"
ec1 = "f348d963-9a36-43ea-a708-603e668b0063"
# Process 2
p2 = 54285
c2 = "5e529def-51fe-4bde-9955-5eca7299bd89"
ec2 = "b9bc824e-5543-4e26-99b3-2387fe4d2942"
target = "370d1a09-1fa8-4aed-90d7-4024e36b3a2d"
args = {"cascadeId": target, "verbosity": 1}
args_alt = {"googleAgentId": target}
args_all = {"limit": 10}
for port, csrf in [(p1, c1), (p1, ec1), (p2, c2), (p2, ec2)]:
res = fetch_ls(port, csrf, "GetCascadeTrajectorySteps", args)
print(f"Port {port} with csrf {csrf[:8]}: GetCascadeTrajectorySteps = {str(res)[:100]}")
res_alt = fetch_ls(port, csrf, "GetCascadeTrajectory", args_alt)
print(f"Port {port} with csrf {csrf[:8]}: GetCascadeTrajectory = {str(res_alt)[:100]}")

29
scratch_diag5.py Normal file
View File

@@ -0,0 +1,29 @@
import urllib.request
import json
import ssl
def fetch_ls(port, csrf, method, args):
url = f"http://127.0.0.1:{port}/exa.language_server_pb.LanguageServerService/{method}"
hs = 'x-antigravity-csrf-token'
req = urllib.request.Request(url, data=json.dumps(args).encode(), headers={
'Content-Type': 'application/json',
hs: csrf
})
try:
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
except Exception as e:
return f"Error: {e}"
p2 = 54285
c2 = "5e529def-51fe-4bde-9955-5eca7299bd89"
target = "370d1a09-1fa8-4aed-90d7-4024e36b3a2d"
args = {
"cascadeId": target,
"verbosity": 1,
"workspaceUri": "file:///c:/Users/Variet-Worker/Desktop/gravity_control"
}
res = fetch_ls(p2, c2, "GetCascadeTrajectorySteps", args)
print(f"GetCascadeTrajectorySteps with workspaceUri = {str(res)[:200]}")

35
scratch_fetch.js Normal file
View File

@@ -0,0 +1,35 @@
const fs = require('fs');
const http = require('http');
const logPath = 'C:\\Users\\Variet-Worker\\.gemini\\antigravity\\bridge\\extension.log';
const log = fs.readFileSync(logPath, 'utf8');
const match = [...log.matchAll(/port:(\d+)/g)].pop();
if (!match) {
console.error('No port found');
process.exit(1);
}
const port = match[1];
console.log(`Port: ${port}`);
const req = http.request(`http://127.0.0.1:${port}/test-rpc`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}, (res) => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
try {
const json = JSON.parse(data);
const recent = json.recentTrajectories || [];
console.log(`recentTrajectories count: ${recent.length}`);
recent.forEach((t, i) => {
console.log(`[${i}] googleAgentId: ${t.googleAgentId} summary: ${t.summary} ws: ${t.trajectoryMetadata?.workspaces?.[0]?.workspaceFolderAbsoluteUri}`);
});
} catch(e) {
console.log(`Error parsing json: ${e.message}`);
console.log(`Raw data: ${data.substring(0, 200)}`);
}
});
});
req.write(JSON.stringify({ method: 'GetDiagnostics', args: {} }));
req.end();

58
scratch_fetch2.js Normal file
View File

@@ -0,0 +1,58 @@
const http = require('http');
const port = 34332;
async function doRPC(method, args) {
return new Promise((resolve) => {
const req = http.request(`http://127.0.0.1:${port}/test-rpc`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}, (res) => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => resolve(JSON.parse(data)));
});
req.write(JSON.stringify({ method, args }));
req.end();
});
}
async function main() {
let allIds = [];
let pageToken = "";
for (let i = 0; i < 5; i++) {
const args = { descending: true };
if (pageToken) args.pageToken = pageToken;
console.log(`Fetching page ${i+1} with pageToken='${pageToken}'...`);
const res = await doRPC('GetAllCascadeTrajectories', args);
if (!res.trajectorySummaries) {
console.log("No summaries:", res);
break;
}
const keys = Object.keys(res.trajectorySummaries);
allIds.push(...keys);
console.log(` Got ${keys.length} items`);
if (keys.length > 0) {
console.log(` First: ${keys[0]}`);
console.log(` Last: ${keys[keys.length-1]}`);
}
if (res.nextPageToken) {
pageToken = res.nextPageToken;
} else {
console.log("No nextPageToken.");
break;
}
}
console.log(`Total collected: ${allIds.length}`);
if (allIds.includes("370d1a09-1fa8-4aed-90d7-4024e36b3a2d")) {
console.log(" FOUND 370d1a09-1fa8-4aed-90d7-4024e36b3a2d !!");
} else {
console.log(" Missing user active session.");
}
}
main();

29
scratch_fetch3.js Normal file
View File

@@ -0,0 +1,29 @@
const http = require('http');
const port = 34332;
function testArgs(args) {
return new Promise((resolve) => {
const req = http.request(`http://127.0.0.1:${port}/test-rpc`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}, (res) => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
console.log(`Args ${JSON.stringify(args)}: ${data.substring(0, 100)}`);
resolve();
});
});
req.write(JSON.stringify({ method: 'GetCascadeTrajectorySteps', args }));
req.end();
});
}
async function run() {
const id = "370d1a09-1fa8-4aed-90d7-4024e36b3a2d";
await testArgs({ cascadeId: id, verbosity: 1 });
await testArgs({ trajectoryId: id, verbosity: 1 });
await testArgs({ id: id, verbosity: 1 });
await testArgs({ googleAgentId: id, verbosity: 1 });
}
run();