Files
gravity_control/git_log.txt

660 lines
72 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
commit 13f13ee243ba50768d8389509f77f03d32989d58
Author: Variet Worker <worker@variet.net>
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 <worker@variet.net>
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 <worker@variet.net>
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<string> {
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<string, any> = {};
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 <worker@variet.net>
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<bidList2.length;di++){delete _sent[bidList2[di]];}
+ return;
+ }
log('Pending created: '+d.request_id+' for group ['+buttonsArr2.map(function(x){return x.text;}).join(', ')+']');
pollResponseGroup(d.request_id,btnRefs2,bidList2,groupKey2);
}).catch(function(e){
diff --git a/extension/src/step-probe.ts b/extension/src/step-probe.ts
index 0b5db13..02fd3e7 100644
--- a/extension/src/step-probe.ts
+++ b/extension/src/step-probe.ts
@@ -405,7 +405,9 @@ function setupMonitor() {
(pd.step_index === ctx.lastPendingStepIndex || (ageMs < 60_000 && ageMs >= 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<void> {
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 <worker@variet.net>
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` | ??|