commit 13f13ee243ba50768d8389509f77f03d32989d58 Author: Variet Worker Date: Wed Apr 1 18:21:51 2026 +0900 fix(extension): resolve 10-item limit truncation & WS zombie disconnection (v0.5.14) diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index c343f77..f993a22 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -29,6 +29,20 @@ ## ?뵶 Active/Recent Issues +### [2026-03-31] [step-probe] GetAllCascadeTrajectories 10-Item Hard Limit (Signal Drop) +- **利앹긽**: `guitar_score` ?깆뿉???쒖꽦?붾맂 ?몄뀡???붿뒪肄붾뱶 ?뱀씤 ?좏샇瑜?"怨꾩냽?댁꽌" ?≪? 紐삵븿. (WS 60珥??€?꾩븘?껊낫????移섎챸?곸쑝濡??좏샇媛€ ?꾩삁 媛€吏€ ?딆쓬) +- **?먯씤**: Extension???쒖꽦 ?몄뀡??李얘린 ?꾪빐 ?몄텧?섎뒗 `GetAllCascadeTrajectories` LS API媛€ `{}`(鍮??몄옄)濡??몄텧???? 湲곕낯?곸쑝濡?**10媛쒖쓽 ?몄뀡留?諛섑솚?섎뒗 ?섎뱶 由щ컠(Pagination Limit)**??嫄몃젮?덉쓬. ?대줈 ?명빐 ?묒뾽 ?댁뿭???꾩쟻?섎㈃ ?섎쭖?€ 理쒖떊/吏꾪뻾 以??몄뀡?ㅼ씠 10媛?紐⑸줉?먯꽌 諛€?ㅻ굹 ?꾨씫?? ?듭뒪?먯뀡?€ ?몄뀡???녿떎怨??먮떒??媛뺤젣濡?`IDLE` 紐⑤뱶??吏꾩엯?섎ʼn, ?뱀씤 ?€湲곗뿴(WAITING) ?먯껜瑜?寃€?ы븯吏€ ?딄쾶 ?? +- **?닿껐** (v0.5.14): `v0.5.13`?먯꽌 ?꾩엯?덈뜕 `{ limit: 100 }`??LS ?⑥쓽 荑쇰━ 怨쇰??섎줈 ?명븳 VS Code UI ?꾨━吏?DoS)???좊컻?섏뿬 濡ㅻ갚?섎뒗 以??꾩닔 ?뺣젹 ?뚮씪誘명꽣(`descending: true`)源뚯? ?뚯떎?섏뿀???ㅼ닔瑜?援먯젙?? 理쒖쥌?곸쑝濡?`{ limit: 30, descending: true }`瑜??곸슜?섏뿬 ?뚯떛 遺€??理쒖냼??諛?理쒖떊 ?몄뀡 理쒖긽??Index 0) 議고쉶瑜??덉쟾?섍쾶 援ы쁽?? +- **二쇱쓽**: LS??湲곕낯 SQLite/DB ?묐떟 Limit 洹쒖튃???섏〈?섏뿬 ?꾩껜 ?곗씠???ㅼ틪???섑뻾?섎뒗 濡쒖쭅?€ ?몄젣??Truncation ?댁뒋(Data Loss)瑜??좊컻?????덉쓬. + +### [2026-03-31] [WS] Browser API Fallback 60s Timeout (Zombie Connection) +- **利앹긽**: `guitar_score` ??紐⑤뱺 ?묒뾽 ?섍꼍?먯꽌 ??60珥덈쭏??WebSocket ?곌껐???딄린怨??ъ뿰寃곕릺???꾩긽??諛섎났?섎ʼn(extension.log??`Heartbeat timeout` 怨꾩냽 異쒕젰), 洹??ъ씠 ?붿뒪肄붾뱶 ?뱀씤 ?좏샇瑜??볦묠. +- **?먯씤**: Extension??`ws` 紐⑤뱢 濡쒕뱶 ?ㅽ뙣(VS Code ?섍꼍 ??濡??명빐 釉뚮씪?곗? ?댁옣 `WebSocket` 媛앹껜濡?Fallback ?? 釉뚮씪?곗? WS???쒕쾭???ㅼ씠?곕툕 ping??諛쏆븘 pong???먮룞 ?묐떟?섏?留?JS???대깽?몃? ?몄텧?섏? ?딆쓬. ?대줈 ?명빐 `lastPongTime` 媛깆떊??遺덇??ν빐?? `Date.now() - lastPongTime > 60000` 議곌굔??臾댁“嫄??듦낵?섏뼱 硫€姨≫븳 ?곌껐??媛뺤젣 醫낅즺??(False Positive). +- **?닿껐** (v0.5.12): + 1. `hub.py`: `{"type": "heartbeat"}` JSON 硫붿떆吏€ ?섏떊 ??紐낆떆?곸쑝濡?`{"type": "pong"}` JSON???묐떟?섎룄濡??섏젙. + 2. `ws-client.ts`: 紐낆떆??`pong` ?몃뱾??異붽?. JSON pong 吏€???쒕쾭嫄곕굹 Node.js ws瑜??ъ슜???뚮쭔 60珥??€?꾩븘??寃€利앹쓣 嫄곗튂?꾨줉 議곌굔 蹂닿컯 (`forceHeartbeatTimeoutIfNoPong`). +- **二쇱쓽**: 釉뚮씪?곗? ?쒖? WebSockets(W3C)??ping/pong ?쒖뼱 ?꾨젅?꾩쓣 JS濡??몄텧?섏? ?딆쓬. ?대━???щ줈?ㅽ뵆?ロ뤌 WS ?섑띁 ?ъ슜 ???섑듃鍮꾪듃??諛섎뱶??JSON 硫붿꽭吏€ ?뺥깭??Application Layer Ping/Pong?쇰줈 ?€?대궡嫄곕굹, Native WS API ?щ?瑜??뺤떎??泥댄겕?댁빞 ?? + ### [2026-03-28] [step-probe] GetCascadeTrajectorySteps UTF-8 ?먮윭 臾댄븳 猷⑦봽 - **利앹긽**: `guitar_score` ?꾨줈?앺듃?먯꽌 `[STEP-PROBE] error: ...invalid UTF-8` ?먮윭媛€ 5珥덈쭏??諛섎났?섎ʼn Discord ?뱀씤 ?좏샇媛€ ?꾨떖?섏? ?딆쓬. - **?먯씤**: AG LS ?쒕쾭?먯꽌 ?뱀젙 step??`CortexStepEphemeralMessage.content`??諛붿씠?덈━ ?곗씠???대?吏€ ?? ?ы븿 ??proto UTF-8 吏곷젹??500 ?먮윭. `catch(e)` 釉붾줉?먯꽌 `stallProbed=true`瑜??ㅼ젙?섏? ?딆븘 `!ctx.stallProbed` 議곌굔????긽 true ??5珥덈쭏???숈씪 ?붿껌 臾댄븳 ?ъ떆?? diff --git a/docs/devlog/2026-04-01.md b/docs/devlog/2026-04-01.md new file mode 100644 index 0000000..6b086b4 --- /dev/null +++ b/docs/devlog/2026-04-01.md @@ -0,0 +1,5 @@ +# 2026-04-01 Devlog + +| NNN | HH:MM | ?묒뾽 ?ㅻ챸 | `而ㅻ컠?댁떆` | ???먮뒗 ?뵩 | +|-------|-------|-----------|-------------|--------------| +| 001 | 18:22 | `step-probe` 10-Item Truncation/DoS ?고쉶 (vsix v0.5.14) | `TBD` | ??| diff --git a/docs/devlog/entries/20260401-001.md b/docs/devlog/entries/20260401-001.md new file mode 100644 index 0000000..3c6e09e --- /dev/null +++ b/docs/devlog/entries/20260401-001.md @@ -0,0 +1,11 @@ +# step-probe Pagination 10-Item Truncation vs LS DoS ?ㅻ쪟 ?섏젙 + +- **?쒓컙**: 2026-04-01 13:00~18:22 +- **Commit**: `TBD` +- **Vikunja**: #N/A (?꾩떆 踰꾧렇 ?쎌뒪) + +## 寃곗젙 ?ы빆 +- 湲곗〈 `v0.5.13`?먯꽌 `limit: 100`?쇰줈 Pagination Limit(湲곕낯 10媛????고쉶?섎젮 ?덉쑝?? LS DB ?ㅼ틪 諛?嫄곕???JSON ?뚯떛??VS Code Event Loop 釉붾줈?뱀쓣 ?좊컻?섏뿬 UI 硫덉땄(DoS) 諛쒖깮. +- 濡ㅻ갚 怨쇱젙?먯꽌 `{}`(?몄옄 ?놁쓬)?쇰줈 ?먮났?섎㈃???꾩닔?곸씤 `descending: true` ?뚮씪誘명꽣源뚯? ?꾨씫?? +- ?대줈 ?명빐 `guitar_score` ?깆쓽 理쒖떊 ?묒꽦 ?몄뀡??LS 議고쉶 由щ컠(10)?먯꽌 諛€?ㅻ굹 ?뱀씤 ?좏샇瑜??섏떊?섏? 紐삵븯???댁뒋 ?щ컻. +- ?대? ?닿껐?섍린 ?꾪빐 `limit: 30, descending: true`濡??ㅼ젙. ?뚯떛?댁빞 ??JSON 媛앹껜 ?섎? 1/3濡?以꾩엫怨??숈떆?? ?뺣젹 蹂댁옣???듯빐 理쒓렐 10珥??대궡???쒖꽦?붾맂 ?몄뀡?€ ?몄젣??Index 0踰?理쒖긽?⑥뿉 怨좎젙?섍쾶??硫붿빱?덉쬁???섏젙?? diff --git a/extension/package.json b/extension/package.json index 0145fbb..35fdce5 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "gravity-bridge", "displayName": "Gravity Bridge", "description": "Antigravity ??Discord 釉뚮━吏€ ?곕룞 ?뺤옣", - "version": "0.5.11", + "version": "0.5.14", "publisher": "variet", "engines": { "vscode": "^1.100.0" diff --git a/extension/src/step-probe.ts b/extension/src/step-probe.ts index 7fa66f5..afc8ed7 100644 --- a/extension/src/step-probe.ts +++ b/extension/src/step-probe.ts @@ -178,7 +178,8 @@ function setupMonitor() { ctx.logToFile(`[POLL#${pollCount}] alive`); } try { - const allTraj = await ctx.sdk.ls.rawRPC('GetAllCascadeTrajectories', {}); + // Fix (v0.5.14): Reverted 100-limit DoS but restored descending: true with a safe limit of 30 + const allTraj = await ctx.sdk.ls.rawRPC('GetAllCascadeTrajectories', { limit: 30, descending: true }); if (!allTraj?.trajectorySummaries) { if (pollCount <= 3) ctx.logToFile('[POLL] no trajectorySummaries'); return; diff --git a/extension/src/ws-client.ts b/extension/src/ws-client.ts index d8f7d96..9907e50 100644 --- a/extension/src/ws-client.ts +++ b/extension/src/ws-client.ts @@ -124,6 +124,7 @@ export class WSBridgeClient { private heartbeatTimer: NodeJS.Timeout | null = null; private authTimer: NodeJS.Timeout | null = null; private lastPongTime: number = 0; + private forceHeartbeatTimeoutIfNoPong = false; // Message queue (survives reconnection) private messageQueue: WSMessage[] = []; @@ -440,6 +441,14 @@ export class WSBridgeClient { break; } + case 'pong': { + // Sent by Hub in response to our 'heartbeat' JSON message + // This is crucial for Browser-style WebSockets that don't expose native ping/pong + this.forceHeartbeatTimeoutIfNoPong = true; + this.lastPongTime = Date.now(); + break; + } + default: this.logFn(`[WS] Unknown message type: ${msg.type}`); } @@ -498,7 +507,8 @@ export class WSBridgeClient { this.heartbeatTimer = setInterval(() => { if (this.ws && this.connected) { // Check for zombie connection (no pong for 60s) - if (Date.now() - this.lastPongTime > 60000) { + const isNodeWs = (typeof this.ws.ping === 'function'); + if ((isNodeWs || this.forceHeartbeatTimeoutIfNoPong) && Date.now() - this.lastPongTime > 60000) { this.logFn('[WS] Heartbeat timeout ??no pong received for 60s (zombie connection), terminating'); if (this.ws) { try { this.ws.terminate(); } catch { try { this.ws.close(); } catch { } } diff --git a/hub.py b/hub.py index e95bff2..3cafcc0 100644 --- a/hub.py +++ b/hub.py @@ -590,7 +590,8 @@ class WSHub: await self._on_brain_event(conn.project, payload) elif msg_type == MsgType.HEARTBEAT: - pass # last_heartbeat already updated above + # Echo back a "pong" so clients without native ping/pong can update their timers + await conn.ws.send_json({"type": "pong"}) else: logger.warning(f"[HUB] Unknown message type: {msg_type} from {conn.conn_id}") diff --git a/install_vsix.py b/install_vsix.py new file mode 100644 index 0000000..260ebe4 --- /dev/null +++ b/install_vsix.py @@ -0,0 +1,20 @@ +import zipfile, shutil, os + +vsix = r"c:\Users\Variet-Worker\Desktop\gravity_control\extension\gravity-bridge-0.5.14.vsix" +dest = os.path.expanduser(r"~\.antigravity\extensions\variet.gravity-bridge-0.5.14") +tmp = r"C:\tmp\vsix_extract" + +if os.path.exists(tmp): + shutil.rmtree(tmp) +os.makedirs(tmp, exist_ok=True) + +with zipfile.ZipFile(vsix, 'r') as z: + z.extractall(tmp) + +src = os.path.join(tmp, "extension") +if os.path.exists(dest): + shutil.rmtree(dest) + +shutil.copytree(src, dest) +print(f"Installed to {dest}") +print("Files:", os.listdir(dest)) diff --git a/test_rpc.js b/test_rpc.js new file mode 100644 index 0000000..b10bf73 --- /dev/null +++ b/test_rpc.js @@ -0,0 +1,31 @@ +const { LSBridge } = require('./extension/out/sdk/ls-bridge'); + +async function test() { + const ls = new LSBridge(); + await ls.connect(); + + console.log("Testing { limit: 5, descending: true }..."); + let start = Date.now(); + const res = await ls._rpc('GetAllCascadeTrajectories', { limit: 5, descending: true }); + let duration = Date.now() - start; + + const summaries = res.trajectorySummaries || {}; + const keys = Object.keys(summaries); + console.log(`Execution time: ${duration}ms`); + console.log(`Returned entries: ${keys.length}`); + + keys.slice(0, 5).forEach((k, idx) => { + const modT = summaries[k].lastModifiedTime || summaries[k].lastModifiedTimestamp || 'UNKNOWN'; + console.log(`[${idx}] id=${k.substring(0,8)} mod=${modT} status=${summaries[k].status}`); + }); + + console.log("\nTesting { limit: 100, descending: true }..."); + start = Date.now(); + const res100 = await ls._rpc('GetAllCascadeTrajectories', { limit: 100, descending: true }); + duration = Date.now() - start; + console.log(`Execution time: ${duration}ms`); + console.log(`Returned entries: ${Object.keys(res100.trajectorySummaries || {}).length}`); + + ls.disconnect(); +} +test(); diff --git a/test_ws_logic.js b/test_ws_logic.js new file mode 100644 index 0000000..230c085 --- /dev/null +++ b/test_ws_logic.js @@ -0,0 +1,50 @@ +// test_ws_logic.js +class FakeWS { + constructor() { + this.msgLog = []; + this.terminated = false; + } + send(msg) { + this.msgLog.push(msg); + } + terminate() { + this.terminated = true; + } + close() { + this.terminated = true; + } +} + +// SIMULATE _startHeartbeat() logic from ws-client.ts v0.5.12 +function testLogic(isNodeWs, serverSendsPong) { + let ws = new FakeWS(); + let connected = true; + let lastPongTime = Date.now(); + let forceHeartbeatTimeoutIfNoPong = serverSendsPong; + let checkCounter = 0; + + // Fast forward 61 seconds in time + let timeElapsed = 61000; + let currentNow = Date.now() + timeElapsed; + + // Simulate heartbeat timeout logic + let conditionMet = false; + if ((isNodeWs || forceHeartbeatTimeoutIfNoPong) && currentNow - lastPongTime > 60000) { + conditionMet = true; + ws.terminate(); + } + + return { + conditionMet: conditionMet, + terminated: ws.terminated + }; +} + +console.log("Scenario 1: Node WS (native ping/pong) MUST enforce 60s timeout:"); +console.log(testLogic(true, false)); // expect true, true + +console.log("\nScenario 2: Browser WS (fallback) + NO JSON PONG FROM SERVER MUST NOT enforce 60s timeout:"); +console.log(testLogic(false, false)); // expect false, false (PREVENTS FALSE POSITIVE) + +console.log("\nScenario 3: Browser WS (fallback) + JSON PONG FROM SERVER MUST enforce 60s timeout:"); +console.log(testLogic(false, true)); // expect true, true (DETECTS ZOMBIE) commit 2d5059d2d5af394573fb199d3f1fcb86c999a363 Author: Variet Worker Date: Sat Mar 28 09:21:10 2026 +0900 chore(ext): version bump 0.5.11 diff --git a/docs/devlog/2026-03-28.md b/docs/devlog/2026-03-28.md index d66f07f..55311c7 100644 --- a/docs/devlog/2026-03-28.md +++ b/docs/devlog/2026-03-28.md @@ -2,4 +2,4 @@ | # | ?쒓컙 | ?묒뾽 | 而ㅻ컠 | ?곹깭 | |---|------|------|------|------| -| 001 | 09:12 | guitar_score step-probe UTF-8 臾댄븳猷⑦봽 ?섏젙 + approval stepIndex 蹂댁젙 (v0.5.11) | pending | ??| +| 001 | 09:12 | guitar_score step-probe UTF-8 臾댄븳猷⑦봽 ?섏젙 + approval stepIndex 蹂댁젙 (v0.5.11) | `7bbd874` | ??#539 | diff --git a/extension/package.json b/extension/package.json index ad55676..0145fbb 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "gravity-bridge", "displayName": "Gravity Bridge", "description": "Antigravity ??Discord 釉뚮━吏€ ?곕룞 ?뺤옣", - "version": "0.5.10", + "version": "0.5.11", "publisher": "variet", "engines": { "vscode": "^1.100.0" commit 7bbd8749d7e3ed0b80ba70e3e519e36c95696acc Author: Variet Worker Date: Sat Mar 28 09:15:11 2026 +0900 fix(extension): guitar_score step-probe UTF-8 loop + approval stepIndex guard (v0.5.11) diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index 75ad1a6..c343f77 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -29,6 +29,18 @@ ## ?뵶 Active/Recent Issues +### [2026-03-28] [step-probe] GetCascadeTrajectorySteps UTF-8 ?먮윭 臾댄븳 猷⑦봽 +- **利앹긽**: `guitar_score` ?꾨줈?앺듃?먯꽌 `[STEP-PROBE] error: ...invalid UTF-8` ?먮윭媛€ 5珥덈쭏??諛섎났?섎ʼn Discord ?뱀씤 ?좏샇媛€ ?꾨떖?섏? ?딆쓬. +- **?먯씤**: AG LS ?쒕쾭?먯꽌 ?뱀젙 step??`CortexStepEphemeralMessage.content`??諛붿씠?덈━ ?곗씠???대?吏€ ?? ?ы븿 ??proto UTF-8 吏곷젹??500 ?먮윭. `catch(e)` 釉붾줉?먯꽌 `stallProbed=true`瑜??ㅼ젙?섏? ?딆븘 `!ctx.stallProbed` 議곌굔????긽 true ??5珥덈쭏???숈씪 ?붿껌 臾댄븳 ?ъ떆?? +- **?닿껐** (v0.5.11): `catch` 釉붾줉?먯꽌 UTF-8 ?먮윭 媛먯? ??`stepOffset=currentCount-20`?쇰줈 fallback ?붿껌. offset???ㅽ뙣 ??`stallProbed=true` ?ㅼ젙?섏뿬 猷⑦봽 李⑤떒. `delta>0` ?대깽??諛쒖깮 ??L433?먯꽌 ?먮룞 由ъ뀑. +- **二쇱쓽**: `stallProbed=true`???곴뎄 Lock???꾨떂 ??`delta>0` ???먮룞 由ъ뀑. UTF-8 ?먮윭??AG ?쒕쾭 痢?臾몄젣(?대?吏€/諛붿씠?덈━ ?곗씠?곌? ephemeral message???ы븿)?대?濡?Extension?먯꽌 graceful fallback留?泥섎━. + +### [2026-03-28] [approval-handler] stepIndex 誘명솗????wrong-stepIndex RPC ??퉬 +- **利앹긽**: DOM observer 寃쎈줈濡?`terminal_command` pending ?앹꽦 ??Discord ?뱀씤 ??`HandleCascadeUserInteraction(stepIndex=0)` ??`"input not registered for step 0"` ??LS reconnect ???ъ떆????DOM click fallback?쇰줈 ?€?? (wrong-LS?€ ?숈씪??利앹긽?대굹 ?ㅻⅨ ?먯씤) +- **?먯씤**: `ctx.lastPendingStepIndex=-1` (step-probe媛€ UTF-8 ?먮윭濡?WAITING 誘멸컧吏€)?꾩뿉??`Math.max(0, -1)=0`?쇰줈 clamp?섏뼱 議댁옱?섏? ?딅뒗 step 0??RPC ?꾩넚. +- **?닿껐** (v0.5.11): `effectiveStepIndex = stepIndex >= 0 ? stepIndex : (lastPendingStepIndex >= 0 ? lastPendingStepIndex : -1)`. `effectiveStepIndex < 0`?대㈃ RPC 釉붾줉 ?꾩껜 skip ??DOM click 吏곹뻾 (湲곗〈怨??숈옉 ?숈씪, LS reconnect ??퉬 ?쒓굅). +- **二쇱쓽**: 湲곗〈 洹쒖튃 #14(`uint32`???뚯닔 湲덉?)?€ 異⑸룎泥섎읆 蹂댁씠?? `effectiveStepIndex=-1`????RPC ?먯껜瑜?**?꾩넚?섏? ?딆쑝誘€濡?* ?꾨컲 ?꾨떂. RPC ?꾩넚 ?쒖뿉???ъ쟾???좏슚??stepIndex留??ъ슜. + ### [2026-03-25] [Architecture] Discord Signal Drop & Extension Freezes - **利앹긽**: ?μ떆媛??먮━鍮꾩? ??蹂듦? ??Discord濡??뱀씤 ?좏샇媛€ ?ㅼ? ?딄굅??VS Code UI媛€ 媛꾪뿉??吏€?띿쟻?쇰줈 硫덉땄(Freeze). - **?먯씤**: diff --git a/docs/devlog/2026-03-28.md b/docs/devlog/2026-03-28.md new file mode 100644 index 0000000..d66f07f --- /dev/null +++ b/docs/devlog/2026-03-28.md @@ -0,0 +1,5 @@ +# Devlog ??2026-03-28 + +| # | ?쒓컙 | ?묒뾽 | 而ㅻ컠 | ?곹깭 | +|---|------|------|------|------| +| 001 | 09:12 | guitar_score step-probe UTF-8 臾댄븳猷⑦봽 ?섏젙 + approval stepIndex 蹂댁젙 (v0.5.11) | pending | ??| diff --git a/extension/src/approval-handler.ts b/extension/src/approval-handler.ts index 4d2b169..22106a1 100644 --- a/extension/src/approval-handler.ts +++ b/extension/src/approval-handler.ts @@ -313,7 +313,8 @@ async function processResponseFile(filePath: string) { */ export async function tryApprovalStrategies(approved: boolean, sessionId: string, stepType: string = '', stepIndex: number = -1): Promise { const action = approved ? 'APPROVE' : 'REJECT'; - const effectiveStepIndex = Math.max(0, stepIndex >= 0 ? stepIndex : ctx.lastPendingStepIndex); + const effectiveStepIndex = stepIndex >= 0 ? stepIndex + : (ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : -1); ctx.logToFile(`[APPROVAL] Starting ${action} strategies for session ${sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${effectiveStepIndex}`); // ?€?€ Dynamic Command Discovery (log what's available during WAITING state) ?€?€ @@ -338,7 +339,7 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 // STRATEGY 0-PROTO: Correct proto-based RPC (decoded from AG source) // ?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧?먥븧 - if (ctx.sdk && approved) { + if (ctx.sdk && approved && effectiveStepIndex >= 0) { // Build interaction sub-message based on step_type const typeLower = stepType.toLowerCase().replace('cortex_step_type_', ''); let interactionPayload: Record = {}; diff --git a/extension/src/step-probe.ts b/extension/src/step-probe.ts index 02fd3e7..7fa66f5 100644 --- a/extension/src/step-probe.ts +++ b/extension/src/step-probe.ts @@ -601,7 +601,79 @@ function setupMonitor() { } } } catch (e: any) { - ctx.logToFile(`[STEP-PROBE] error: ${e.message}`); + ctx.logToFile(`[STEP-PROBE] error: ${e.message?.substring(0, 150)}`); + // UTF-8 invalid data in a step causes a permanent 500 error on full fetch. + // Attempt stepOffset to skip that step and fetch only recent steps. + const isUtf8Error = e.message?.includes('invalid UTF-8') || e.message?.includes('proto:'); + if (isUtf8Error && ctx.sdk) { + try { + const utf8Offset = Math.max(0, currentCount - 20); + ctx.logToFile(`[STEP-PROBE] UTF-8 fallback: retrying with stepOffset=${utf8Offset}`); + const offsetResp = await ctx.sdk.ls.rawRPC('GetCascadeTrajectorySteps', { + cascadeId: bestSessionId, + stepOffset: utf8Offset, + verbosity: 1, + }); + if (offsetResp?.steps?.length > 0) { + const offsetSteps = offsetResp.steps; + ctx.logToFile(`[STEP-PROBE] UTF-8 offset=${utf8Offset} returned ${offsetSteps.length} steps`); + let foundWaitingInOffset = false; + for (let osi = offsetSteps.length - 1; osi >= 0; osi--) { + const oStep = offsetSteps[osi]; + if (oStep?.status === 'CORTEX_STEP_STATUS_WAITING') { + foundWaitingInOffset = true; + const toolCall = oStep?.metadata?.toolCall; + const toolName = toolCall?.name || (oStep.type || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase(); + let command = toolName; + if (toolCall?.argumentsJson) { + try { + const args = JSON.parse(toolCall.argumentsJson); + if (args.CommandLine) command = `${toolName}: ${args.CommandLine.substring(0, 1500)}`; + else if (args.TargetFile) command = `${toolName}: ${args.TargetFile}`; + else { + const val = args.DirectoryPath || args.SearchPath || args.AbsolutePath || args.Url || args.Query || args.Prompt || Object.values(args).find((v: any) => typeof v === 'string' && v.length > 2); + command = val ? `${toolName}: ${String(val).substring(0, 500)}` : `${toolName}: ${Object.keys(args).join(', ')}`; + } + } catch { command = toolName; } + } + const actualIndex = utf8Offset + osi; + ctx.logToFile(`[STEP-PROBE] ??WAITING (via UTF-8 offset)! step=${actualIndex} type=${oStep.type} cmd='${command}'`); + if (actualIndex !== ctx.lastPendingStepIndex) { + ctx.stallProbed = true; + if (actualIndex > ctx.lastPendingStepIndex) ctx.lastPendingStepIndex = actualIndex; + lastPendingTime = Date.now(); + ctx.sawRunningAfterPending = false; + if (ctx.projectName !== 'default') { + writePendingApproval({ + conversation_id: ctx.activeSessionId, + command, + description: `Step #${actualIndex} (${(oStep.type || '').replace('CORTEX_STEP_TYPE_', '')})`, + 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: actualIndex, + source: 'step_probe_utf8_offset', + }); + } + } + // NOTE: no break ??process ALL parallel WAITING steps + } + } + if (!foundWaitingInOffset) { + ctx.logToFile(`[STEP-PROBE] UTF-8 offset: no WAITING found ??stallProbed=true to prevent loop`); + ctx.stallProbed = true; // prevent retry loop; resets on delta>0 + ctx.sessionStalled = false; + } + } else { + ctx.logToFile(`[STEP-PROBE] UTF-8 offset returned empty ??stallProbed=true`); + ctx.stallProbed = true; + } + } catch (oe: any) { + ctx.logToFile(`[STEP-PROBE] UTF-8 offset also failed: ${oe.message?.substring(0, 100)}`); + ctx.stallProbed = true; // permanent error ??block retry loop; resets on delta>0 + } + } } } commit d5fdc41f35d0d206114a036343ee049d62421f6b Author: Variet Worker Date: Wed Mar 25 07:14:34 2026 +0900 fix(extension): Discord signal drop and UI freeze (async IO, regex filters, WS rate-limits) (v0.5.10) diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index 1d9a2a8..75ad1a6 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -29,6 +29,15 @@ ## ?뵶 Active/Recent Issues +### [2026-03-25] [Architecture] Discord Signal Drop & Extension Freezes +- **利앹긽**: ?μ떆媛??먮━鍮꾩? ??蹂듦? ??Discord濡??뱀씤 ?좏샇媛€ ?ㅼ? ?딄굅??VS Code UI媛€ 媛꾪뿉??吏€?띿쟻?쇰줈 硫덉땄(Freeze). +- **?먯씤**: + 1. `ws.onerror` 諛쒖깮 ??`onclose` ?꾨씫 ???ъ뿰寃?肄쒕갚 ?몄텧???대(?댁?吏€ ?딆븘 臾댄븳 ?€湲?(?μ떆媛?留덈퉬) + 2. `ws-client` ?ъ뿰寃????꾩쟻??200媛??먮? ?숆린??burst ?꾩넚?섏뿬 Hub???띾룄 ?쒗븳(60媛?10珥???嫄몃젮 ?뺤젙 ?곴뎄 ??젣??+ 3. 濡쒖뺄 釉뚮┸吏€ `http-bridge.ts`??怨쇨굅 ?좎궛??`FALSE_POSITIVE_RE` ?뺢퇋?앹씠 AI 怨좎쑀 踰꾪듉(Allow, Deny, Accept) 留덉? ?꾪꽣留곹븯??Discord ?꾩넚 ?먯쿇 李⑤떒 + 4. `step-probe.ts` ?대쭅 猷⑦봽 ???숆린???뚯씪 I/O ?ъ슜?쇰줈 ?명븳 ?꾨━利?+- **?닿껐** (v0.5.10): ws-client???섎뱶 ?€?꾩븘??諛?50ms Paced-flush ?곸슜, http-bridge???뺢퇋??湲곕뒫 ?꾪솕, step-probe 鍮꾨룞湲?I/O ?꾪솚 泥댁젣 ?곸슜, observer-script???꾪꽣???좏샇 臾댄븳 HTTP ?대쭅 諛⑹뼱 肄붾뱶 諛섏쁺. +- **二쇱쓽**: Extension ?대? 濡쒖쭅 踰꾧렇?€?쇰?濡?Hub(Python) 肄붾뱶??嫄대뱶由ъ? ?딆쓬. Hub ?띾룄 ?쒗븳?€ ?뺤긽 諛⑹뼱 湲곗젣?대?濡??대씪?댁뼵???⑥쓽 Pacing???щ컮瑜?諛⑺뼢?? ### [2026-03-24] DOM Observer /trigger-click ?뚮뜑留??쒖꽌 ?ㅼ옉??諛?False Positive ?꾨━吏? - **利앹긽**: v0.5.9 ?⑥튂 ?댄썑 肄붾뵫 ??Agent ?붾㈃???딆엫?놁씠 ?쒕챸 ?€湲?Pending) ?곹깭濡?硫덉땄. ?먮뒗 ?붿뒪肄붾뱶?먯꽌 `Approve` ???먮뵒???댁쓽 ?됰슧??`Run Test`(肄붾뱶 ?뚯쫰)瑜??대┃?? - **?먯씤**: ?띿뒪?몄? ?뺢퇋??`/^Run/i` ???먮쭔 ?섏〈?섏뿬 `querySelectorAll`???섑뻾??寃쎌슦, DOM ?몃━???뚮뜑留곷맂 ?섎쭖?€ VS Code ?ㅼ씠?곕툕 肄붾뱶 ?뚯쫰 踰꾪듉??Agent 踰꾪듉蹂대떎 癒쇱? 李얠븘踰꾨━??諛쒖깮 ?꾩튂(Context)???쒓퀎?? diff --git a/docs/devlog/2026-03-25.md b/docs/devlog/2026-03-25.md new file mode 100644 index 0000000..05e069e --- /dev/null +++ b/docs/devlog/2026-03-25.md @@ -0,0 +1,5 @@ +# 2026-03-25 Devlog + +| NNN | HH:MM | ?묒뾽 ?ㅻ챸 | `而ㅻ컠?댁떆` | ???먮뒗 ?뵩 | +|-----|-------|----------|-----------|-----------| +| 001 | 07:15 | ws-client reconnect pacing 諛?http-bridge ?뺢퇋???꾪꽣 ?꾪솕濡?Signal Drop ?닿껐 (v0.5.10) | `pending` | ??| diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts index 3833686..6d1d6a3 100644 --- a/extension/src/http-bridge.ts +++ b/extension/src/http-bridge.ts @@ -189,7 +189,8 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { // ?€?€ Server-side false positive filter ?€?€ const cmd = (data.command || '').trim(); - const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it|Deny|Allow Once|Allow This Conversation|Dismiss|Decline|Accept|Reject|Accept all|Reject all)$/i; + // Removed valid AI buttons (Accept, Reject, Allow, Deny) which are now structurally protected by the observer script + const FALSE_POSITIVE_RE = /^(Proceed|Continue|Open|Close|OK|Yes|No|Save|Undo|Redo|Back|Next|More|Less|Got it|Dismiss)$/i; if (FALSE_POSITIVE_RE.test(cmd)) { ctx.logToFile(`[HTTP] filtered false positive: "${cmd}"`); res.writeHead(200, { 'Content-Type': 'application/json' }); diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index bd29ed7..cf81472 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -479,6 +479,12 @@ export function generateApprovalObserverScript(_port: number): string { headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload) }).then(function(r){return r.json();}).then(function(d){ + if (!d.ok || d.filtered) { + log('Pending rejected/filtered for group ['+buttonsArr2.map(function(x){return x.text;}).join(', ')+']'); + delete _sent[groupKey2]; + for(var di=0;di= 0)); if (isMatch) { pd.status = 'auto_resolved'; - fs.writeFileSync(pfPath, JSON.stringify(pd, null, 2), 'utf-8'); + fs.promises.writeFile(pfPath, JSON.stringify(pd, null, 2), 'utf-8').catch(e => { + ctx.logToFile(`[AUTO-RESOLVE] write error: ${e.message}`); + }); resolvedCount++; const cmd = pd.command || ''; if (cmd.length > primaryCommand.length && cmd !== 'Deny' && !cmd.includes('Allow')) { @@ -989,7 +991,9 @@ export function writePendingApproval(data: { conversation_id: string; command: s if (data.step_type) existing.step_type = data.step_type; if (data.step_index !== undefined) existing.step_index = data.step_index; existing.source = 'dom_observer+step_probe'; // mark as merged - fs.writeFileSync(efPath, JSON.stringify(existing, null, 2), 'utf-8'); + fs.promises.writeFile(efPath, JSON.stringify(existing, null, 2), 'utf-8').catch(e => { + ctx.logToFile(`[DEDUP] merge write error: ${e.message}`); + }); ctx.logToFile(`[DEDUP] MERGED step_probe info into DOM pending: ${ef} cmd="${data.command.substring(0, 60)}"`); // Record in memory dedup if (data.step_index !== undefined && data.conversation_id) { @@ -1071,7 +1075,9 @@ export function writePendingApproval(data: { conversation_id: string; command: s return; } // File route (fallback ??only when WS is NOT connected) - fs.writeFileSync(path.join(pendingDir, `${id}.json`), JSON.stringify(payload, null, 2), 'utf-8'); + fs.promises.writeFile(path.join(pendingDir, `${id}.json`), JSON.stringify(payload, null, 2), 'utf-8').catch(e => { + console.error(`Gravity Bridge: failed to write pending: ${e.message}`); + }); console.log(`Gravity Bridge: pending approval written ??${id}.json`); // Cache diff_review metadata in-memory (survives pending file deletion by Collector/Bot) if (data.step_type === 'diff_review' && (data.edit_step_indices?.length || data.modified_files?.length)) { diff --git a/extension/src/ws-client.ts b/extension/src/ws-client.ts index a5cba89..d8f7d96 100644 --- a/extension/src/ws-client.ts +++ b/extension/src/ws-client.ts @@ -213,12 +213,21 @@ export class WSBridgeClient { this.logFn(`[WS] Connecting to ${this.hubUrl}...`); const ws = new WebSocket(this.hubUrl); + let connectTimeout: NodeJS.Timeout | null = null; + const clearConnectTimeout = () => { + if (connectTimeout) { + clearTimeout(connectTimeout); + connectTimeout = null; + } + }; + // Detect API style: Node.js 'ws' module has .on(), browser WebSocket doesn't const isNodeWs = typeof ws.on === 'function'; if (isNodeWs) { // ?€?€?€ Node.js ws module (EventEmitter API) ?€?€?€ ws.on('open', () => { + clearConnectTimeout(); this.logFn('[WS] Connection opened, authenticating...'); this.ws = ws; this.connected = true; @@ -235,11 +244,18 @@ export class WSBridgeClient { }); ws.on('close', (code: number, reason: Buffer) => { + clearConnectTimeout(); const reasonStr = reason ? reason.toString('utf-8') : ''; this.logFn(`[WS] Connection closed: code=${code} reason=${reasonStr}`); this._onDisconnect(); }); + ws.on('error', (err: any) => { + clearConnectTimeout(); + this.logFn(`[WS] Connection error: ${err.message || err}`); + this._onDisconnect(); + }); + ws.on('pong', () => { // Server responded to our ping ??connection is alive this.lastPongTime = Date.now(); @@ -247,6 +263,7 @@ export class WSBridgeClient { } else { // ?€?€?€ Browser-style WebSocket API (.onopen / .onmessage) ?€?€?€ ws.onopen = () => { + clearConnectTimeout(); this.logFn('[WS] Connection opened (browser API), authenticating...'); this.ws = ws; this.connected = true; @@ -264,15 +281,29 @@ export class WSBridgeClient { }; ws.onclose = (event: any) => { + clearConnectTimeout(); this.logFn(`[WS] Connection closed: code=${event.code} reason=${event.reason || ''}`); this._onDisconnect(); }; ws.onerror = (event: any) => { + clearConnectTimeout(); this.logFn(`[WS] Error: ${event.message || 'connection error'}`); + this._onDisconnect(); }; } + // Connection timeout to prevent hanging if no close/error fires + connectTimeout = setTimeout(() => { + this.logFn('[WS] Connection timeout (15s) ??forcing disconnect'); + if (this.ws) { + try { this.ws.terminate(); } catch { try { this.ws.close(); } catch { } } + } else if (ws) { + try { ws.terminate(); } catch { try { ws.close(); } catch { } } + } + this._onDisconnect(); + }, 15000); + } catch (e: any) { this.logFn(`[WS] Connect failed: ${e.message}`); this._scheduleReconnect(); @@ -448,13 +479,15 @@ export class WSBridgeClient { } } - private _flushQueue(): void { + private async _flushQueue(): Promise { if (this.messageQueue.length === 0) return; - this.logFn(`[WS] Flushing ${this.messageQueue.length} queued messages`); + this.logFn(`[WS] Flushing ${this.messageQueue.length} queued messages (paced)`); const queue = [...this.messageQueue]; this.messageQueue = []; for (const msg of queue) { this._sendRaw(msg); + // Pace the burst to avoid hitting the Hub's rate limit (60 msgs / 10s) + await new Promise(r => setTimeout(r, 50)); } } commit 3ec45ac6b7ec9779181fac99948f6999ae8d29e0 Author: Variet Worker Date: Tue Mar 24 18:19:30 2026 +0900 docs(devlog): record hash and Vikunja ID for session 001 and 003 diff --git a/docs/devlog/2026-03-24.md b/docs/devlog/2026-03-24.md index 40b8359..f854f3f 100644 --- a/docs/devlog/2026-03-24.md +++ b/docs/devlog/2026-03-24.md @@ -2,6 +2,6 @@ | NNN | HH:MM | ?묒뾽 ?ㅻ챸 | `而ㅻ컠?댁떆` | ???먮뒗 ?뵩 | |-----|-------|----------|-----------|-----------| -| 001 | 07:05 | v0.5.6 醫€鍮?而ㅻ꽖???⑥튂 ?뚭? ?ㅻ쪟 ?닿껐 (False Positive ?딄? 諛⑹?瑜??꾪븳 ?€?꾩뒪?ы봽 寃€利??꾩엯 v0.5.8) | `TBD` | ??| +| 001 | 07:05 | v0.5.6 醫€鍮?而ㅻ꽖???⑥튂 ?뚭? ?ㅻ쪟 ?닿껐 (False Positive ?딄? 諛⑹?瑜??꾪븳 ?€?꾩뒪?ы봽 寃€利??꾩엯 v0.5.8) | `f13bcc8` | ??| | 002 | 13:00 | DOM Observer VS Code ?ㅼ씠?곕툕 ?뚮┝ UI 罹≪쿂 釉붾씪?몃뱶 ?ㅽ뙚 ?닿껐 (v0.5.9) | `7b6cd59` | ??| -| 003 | 18:14 | DOM Observer /trigger-click ?뚮뜑留??쒖꽌 ?ㅼ옉??諛?False Positive ?꾨━吏??닿껐 (v0.5.10) | `TBD` | ??| +| 003 | 18:14 | DOM Observer /trigger-click ?뚮뜑留??쒖꽌 ?ㅼ옉??諛?False Positive ?꾨━吏??닿껐 (v0.5.10) | `101ec20` | ??|