@hienlh/ppm 0.8.72 → 0.8.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.73] - 2026-04-01
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Cloud WS reconnect loop**: Stale WebSocket closure handlers from replaced connections no longer reset module state — prevents infinite reconnect cycle after upgrade/restart
|
|
7
|
+
|
|
3
8
|
## [0.8.72] - 2026-03-31
|
|
4
9
|
|
|
5
10
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hienlh/ppm",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.73",
|
|
4
4
|
"description": "Personal Project Manager — mobile-first web IDE with AI assistance",
|
|
5
5
|
"author": "hienlh",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"dev": "concurrently \"bun run dev:server\" \"bun run dev:web\"",
|
|
14
|
-
"dev:server": "bun run --hot src/index.ts
|
|
14
|
+
"dev:server": "bun run --hot src/server/index.ts __serve__ 8081 0.0.0.0 '' dev",
|
|
15
15
|
"dev:web": "bun run vite --config vite.config.ts",
|
|
16
16
|
"build:web": "bun run vite build --config vite.config.ts",
|
|
17
17
|
"build": "bun run build:web && bun build src/index.ts --compile --outfile dist/ppm",
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
} from "./provider.interface.ts";
|
|
14
14
|
import { configService } from "../services/config.service.ts";
|
|
15
15
|
import { updateFromSdkEvent } from "../services/claude-usage.service.ts";
|
|
16
|
-
import { getSessionMapping, setSessionMapping, getSessionTitles } from "../services/db.service.ts";
|
|
16
|
+
import { getSessionMapping, setSessionMapping, getSessionTitles, getSessionTitle } from "../services/db.service.ts";
|
|
17
17
|
import { accountSelector } from "../services/account-selector.service.ts";
|
|
18
18
|
import { accountService } from "../services/account.service.ts";
|
|
19
19
|
import { resolve } from "node:path";
|
|
@@ -176,10 +176,11 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
176
176
|
(s) => s.sessionId === sessionId || s.sessionId === mappedSdkId,
|
|
177
177
|
);
|
|
178
178
|
if (found) {
|
|
179
|
+
const dbTitle = getSessionTitle(found.sessionId);
|
|
179
180
|
const meta: Session = {
|
|
180
181
|
id: sessionId,
|
|
181
182
|
providerId: this.id,
|
|
182
|
-
title: found.customTitle ?? found.summary ?? "Resumed Chat",
|
|
183
|
+
title: dbTitle ?? found.customTitle ?? found.summary ?? "Resumed Chat",
|
|
183
184
|
createdAt: new Date(found.lastModified).toISOString(),
|
|
184
185
|
};
|
|
185
186
|
this.activeSessions.set(sessionId, meta);
|
|
@@ -125,20 +125,24 @@ function doConnect(): void {
|
|
|
125
125
|
if (!shouldConnect || reconnecting) return;
|
|
126
126
|
reconnecting = true;
|
|
127
127
|
|
|
128
|
+
// Capture local ref — if a reconnect replaces `ws` before this socket's
|
|
129
|
+
// handlers fire, stale handlers must not reset module-level state.
|
|
130
|
+
let sock: WebSocket;
|
|
128
131
|
try {
|
|
129
|
-
|
|
132
|
+
sock = new WebSocket(wsUrl);
|
|
133
|
+
ws = sock;
|
|
130
134
|
} catch {
|
|
131
135
|
reconnecting = false;
|
|
132
|
-
scheduleReconnect();
|
|
136
|
+
scheduleReconnect("constructor");
|
|
133
137
|
return;
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
|
|
140
|
+
sock.onopen = () => {
|
|
141
|
+
if (ws !== sock) return; // stale — newer connection replaced us
|
|
137
142
|
reconnecting = false;
|
|
138
143
|
log("INFO", "Cloud WS connected, sending auth");
|
|
139
144
|
|
|
140
|
-
|
|
141
|
-
ws!.send(JSON.stringify({
|
|
145
|
+
sock.send(JSON.stringify({
|
|
142
146
|
type: "auth",
|
|
143
147
|
deviceId,
|
|
144
148
|
secretKey,
|
|
@@ -152,7 +156,7 @@ function doConnect(): void {
|
|
|
152
156
|
// Flush queued messages
|
|
153
157
|
while (outboundQueue.length > 0 && connected) {
|
|
154
158
|
const msg = outboundQueue.shift()!;
|
|
155
|
-
|
|
159
|
+
sock.send(JSON.stringify(msg));
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
// Send immediate heartbeat
|
|
@@ -165,7 +169,7 @@ function doConnect(): void {
|
|
|
165
169
|
}, HEARTBEAT_INTERVAL_MS);
|
|
166
170
|
};
|
|
167
171
|
|
|
168
|
-
|
|
172
|
+
sock.onmessage = (event) => {
|
|
169
173
|
try {
|
|
170
174
|
const msg = JSON.parse(String(event.data)) as CommandMsg;
|
|
171
175
|
if (msg.type === "command" && commandHandler) {
|
|
@@ -174,27 +178,29 @@ function doConnect(): void {
|
|
|
174
178
|
} catch {} // ignore malformed
|
|
175
179
|
};
|
|
176
180
|
|
|
177
|
-
|
|
181
|
+
sock.onclose = (event) => {
|
|
182
|
+
if (ws !== sock) return; // stale — ignore close from replaced connection
|
|
183
|
+
log("WARN", `Cloud WS closed: code=${event.code} reason=${event.reason || ""}`);
|
|
178
184
|
connected = false;
|
|
179
185
|
reconnecting = false;
|
|
180
186
|
ws = null;
|
|
181
187
|
if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
|
|
182
|
-
if (shouldConnect) scheduleReconnect();
|
|
188
|
+
if (shouldConnect) scheduleReconnect("onclose");
|
|
183
189
|
};
|
|
184
190
|
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
sock.onerror = (event) => {
|
|
192
|
+
log("ERROR", `Cloud WS error: ${String(event)}`);
|
|
187
193
|
};
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
function scheduleReconnect(): void {
|
|
196
|
+
function scheduleReconnect(source = "unknown"): void {
|
|
191
197
|
if (!shouldConnect || reconnectTimer) return;
|
|
192
198
|
const base = BACKOFF_STEPS[Math.min(reconnectAttempt, BACKOFF_STEPS.length - 1)]!;
|
|
193
199
|
// Add ±30% jitter to prevent thundering herd after Cloud deploy
|
|
194
200
|
const jitter = base * (0.7 + Math.random() * 0.6);
|
|
195
201
|
const delay = Math.round(jitter);
|
|
196
202
|
reconnectAttempt++;
|
|
197
|
-
log("WARN", `Cloud WS reconnect in ${delay}ms (attempt #${reconnectAttempt})`);
|
|
203
|
+
log("WARN", `Cloud WS reconnect in ${delay}ms (attempt #${reconnectAttempt}) src=${source}`);
|
|
198
204
|
reconnectTimer = setTimeout(() => {
|
|
199
205
|
reconnectTimer = null;
|
|
200
206
|
doConnect();
|