@vibedeckx/linux-x64 0.3.33 → 0.3.35
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/dist/bin.js +218 -55
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -186887,7 +186887,8 @@ var mapLocalActivity = (row) => {
|
|
|
186887
186887
|
parseActivityTimestamp(row.created_at) ?? 0
|
|
186888
186888
|
),
|
|
186889
186889
|
lastUserMessageAt: row.last_user_message_at,
|
|
186890
|
-
lastCompletedAt: row.last_completed_at
|
|
186890
|
+
lastCompletedAt: row.last_completed_at,
|
|
186891
|
+
favoritedAt: row.favorited_at
|
|
186891
186892
|
};
|
|
186892
186893
|
};
|
|
186893
186894
|
var observeLocalActivity = (consumer, row) => {
|
|
@@ -186915,6 +186916,7 @@ var localActivityBase = (kdb, projectId) => {
|
|
|
186915
186916
|
"s.updated_at",
|
|
186916
186917
|
"s.last_user_message_at",
|
|
186917
186918
|
"s.last_completed_at",
|
|
186919
|
+
"s.favorited_at",
|
|
186918
186920
|
"checkout.worktree_path",
|
|
186919
186921
|
"checkout.deleted_at as checkout_deleted_at",
|
|
186920
186922
|
"checkout.status as checkout_status",
|
|
@@ -187196,6 +187198,12 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
187196
187198
|
await observeDanglingLocalScope(kdb, consumer, projectId);
|
|
187197
187199
|
return rows.map(mapLocalActivity);
|
|
187198
187200
|
},
|
|
187201
|
+
listFavoritedActivityByProject: async (projectId, limit, consumer) => {
|
|
187202
|
+
const rows = await localActivityBase(kdb, projectId).where(visibleLifecycleOf("s")).where("s.favorited_at", "is not", null).orderBy("s.favorited_at", "desc").orderBy("s.id", "desc").limit(limit).execute();
|
|
187203
|
+
rows.forEach((row) => observeLocalActivity(consumer, row));
|
|
187204
|
+
await observeDanglingLocalScope(kdb, consumer, projectId);
|
|
187205
|
+
return rows.map(mapLocalActivity);
|
|
187206
|
+
},
|
|
187199
187207
|
countRunningByProject: async (projectId) => {
|
|
187200
187208
|
const row = await kdb.selectFrom("agent_sessions").select(kdb.fn.countAll().as("count")).where("project_id", "=", projectId).where("status", "=", "running").where(visibleLifecycle).executeTakeFirstOrThrow();
|
|
187201
187209
|
return Number(row.count);
|
|
@@ -188166,7 +188174,8 @@ var mapRemoteActivity = (row) => {
|
|
|
188166
188174
|
model: row.model,
|
|
188167
188175
|
lastActiveAt: row.last_active_at,
|
|
188168
188176
|
lastUserMessageAt: row.last_user_message_at,
|
|
188169
|
-
lastCompletedAt: row.last_completed_at
|
|
188177
|
+
lastCompletedAt: row.last_completed_at,
|
|
188178
|
+
favoritedAt: row.favorited_at
|
|
188170
188179
|
};
|
|
188171
188180
|
};
|
|
188172
188181
|
var observeRemoteActivity = (consumer, row) => {
|
|
@@ -188234,6 +188243,7 @@ var remoteActivityBase = (kdb, projectId) => remoteSessionScope(kdb, projectId).
|
|
|
188234
188243
|
"c.model",
|
|
188235
188244
|
"c.last_user_message_at",
|
|
188236
188245
|
"c.last_completed_at",
|
|
188246
|
+
"c.favorited_at",
|
|
188237
188247
|
"checkout.worktree_path",
|
|
188238
188248
|
"checkout.deleted_at as checkout_deleted_at",
|
|
188239
188249
|
"checkout.status as checkout_status",
|
|
@@ -188269,6 +188279,12 @@ var createSearchCacheRepos = (kdb, _h) => ({
|
|
|
188269
188279
|
await observeDanglingRemoteActivity(kdb, consumer, projectId);
|
|
188270
188280
|
return rows.map(mapRemoteActivity);
|
|
188271
188281
|
},
|
|
188282
|
+
listRemoteSessionFavoritesByProject: async (projectId, limit, consumer) => {
|
|
188283
|
+
const rows = await remoteActivityBase(kdb, projectId).where("c.favorited_at", "is not", null).orderBy("c.favorited_at", "desc").orderBy("c.local_session_id", "asc").limit(limit).execute();
|
|
188284
|
+
rows.forEach((row) => observeRemoteActivity(consumer, row));
|
|
188285
|
+
await observeDanglingRemoteActivity(kdb, consumer, projectId);
|
|
188286
|
+
return rows.map(mapRemoteActivity);
|
|
188287
|
+
},
|
|
188272
188288
|
countRemoteSessionActivityByProject: async (projectId) => {
|
|
188273
188289
|
const row = await remoteSessionScope(kdb, projectId).select([
|
|
188274
188290
|
sql`coalesce(sum(case when c.status = 'running' then 1 else 0 end), 0)`.as("running")
|
|
@@ -188450,6 +188466,39 @@ var createSearchCacheRepos = (kdb, _h) => ({
|
|
|
188450
188466
|
updateCachedSessionTitle: async (localSessionId, title) => {
|
|
188451
188467
|
await kdb.updateTable("session_search_cache").set({ title, written_at: Date.now() }).where("local_session_id", "=", localSessionId).execute();
|
|
188452
188468
|
},
|
|
188469
|
+
// Star write-through. Unlike the title and delete write-throughs this one
|
|
188470
|
+
// may CREATE the row: the remote favorite PATCH has already succeeded on
|
|
188471
|
+
// the worker, so the session provably exists, and the path that lists a
|
|
188472
|
+
// remote's sessions (where the star button lives) binds only a mapping —
|
|
188473
|
+
// no cache row. Before that target's first catalog snapshot there would be
|
|
188474
|
+
// nothing to update and the star would sit invisible, which is the exact
|
|
188475
|
+
// latency this write-through exists to remove. Only starring creates a
|
|
188476
|
+
// row; an unstar with no row has nothing to hide (see noteSessionDeleted).
|
|
188477
|
+
updateCachedSessionFavorited: async (localSessionId, favoritedAt) => {
|
|
188478
|
+
const now3 = Date.now();
|
|
188479
|
+
const updated = await kdb.updateTable("session_search_cache").set({ favorited_at: favoritedAt, written_at: now3 }).where("local_session_id", "=", localSessionId).executeTakeFirst();
|
|
188480
|
+
if (Number(updated?.numUpdatedRows ?? 0) > 0 || favoritedAt === null) return;
|
|
188481
|
+
const mapping = await kdb.selectFrom("remote_session_mappings").select(["project_id", "remote_server_id", "branch"]).where("local_session_id", "=", localSessionId).executeTakeFirst();
|
|
188482
|
+
if (!mapping || mapping.remote_server_id === "local") return;
|
|
188483
|
+
await kdb.insertInto("session_search_cache").values({
|
|
188484
|
+
local_session_id: localSessionId,
|
|
188485
|
+
project_id: mapping.project_id,
|
|
188486
|
+
target_id: mapping.remote_server_id,
|
|
188487
|
+
branch: toDbBranch(mapping.branch),
|
|
188488
|
+
title: null,
|
|
188489
|
+
last_active_at: null,
|
|
188490
|
+
favorited_at: favoritedAt,
|
|
188491
|
+
entry_count: 0,
|
|
188492
|
+
status: "unknown",
|
|
188493
|
+
agent_type: null,
|
|
188494
|
+
model: null,
|
|
188495
|
+
last_user_message_at: null,
|
|
188496
|
+
last_completed_at: null,
|
|
188497
|
+
generation: 0,
|
|
188498
|
+
deleted_at: null,
|
|
188499
|
+
written_at: now3
|
|
188500
|
+
}).onConflict((oc) => oc.column("local_session_id").doUpdateSet({ favorited_at: favoritedAt, written_at: now3 })).execute();
|
|
188501
|
+
},
|
|
188453
188502
|
// Create write-through: called where a remote session's creation transits
|
|
188454
188503
|
// the server (UI create proxy, commander spawn, branch-from-history). The
|
|
188455
188504
|
// written_at stamp keeps the row exempt from snapshot reconciliation until
|
|
@@ -240344,7 +240393,7 @@ ${opts.reviewFocus}` : null,
|
|
|
240344
240393
|
## Scope \u2014 the change under review
|
|
240345
240394
|
|
|
240346
240395
|
The reviewed turn changed exactly these files:
|
|
240347
|
-
${scope.changedFiles.map((f2) => `-
|
|
240396
|
+
${scope.changedFiles.map((f2) => `- \`${f2}\``).join("\n")}
|
|
240348
240397
|
|
|
240349
240398
|
It starts from commit \`${scope.startHead}\` \u2014 use \`git diff ${scope.startHead} -- <file>\` and \`git log ${scope.startHead}..HEAD\` to see the content.
|
|
240350
240399
|
|
|
@@ -251385,6 +251434,16 @@ var routes11 = async (fastify2) => {
|
|
|
251385
251434
|
`/api/agent-sessions/${remoteInfo.remoteSessionId}/favorite`,
|
|
251386
251435
|
{ favorited }
|
|
251387
251436
|
);
|
|
251437
|
+
if (result.ok) {
|
|
251438
|
+
try {
|
|
251439
|
+
await fastify2.storage.searchCache.updateCachedSessionFavorited(
|
|
251440
|
+
req.params.sessionId,
|
|
251441
|
+
favorited ? Date.now() : null
|
|
251442
|
+
);
|
|
251443
|
+
} catch (err) {
|
|
251444
|
+
console.error("[API] searchCache.updateCachedSessionFavorited failed:", err);
|
|
251445
|
+
}
|
|
251446
|
+
}
|
|
251388
251447
|
return reply.code(proxyStatus(result)).send(result.data);
|
|
251389
251448
|
}
|
|
251390
251449
|
const session = await fastify2.storage.agentSessions.getById(req.params.sessionId);
|
|
@@ -252467,17 +252526,19 @@ var RECENT_SESSION_LIMIT = 8;
|
|
|
252467
252526
|
var RECENT_RUN_LIMIT = 5;
|
|
252468
252527
|
var PRIORITY_TASK_LIMIT = 5;
|
|
252469
252528
|
var ATTENTION_LIMIT = 10;
|
|
252529
|
+
var STARRED_LIMIT = 50;
|
|
252470
252530
|
var parseDbTimestamp4 = (value) => {
|
|
252471
252531
|
if (!value) return null;
|
|
252472
252532
|
const explicitZone = /(?:Z|[+-]\d\d:\d\d)$/i.test(value);
|
|
252473
252533
|
const parsed = Date.parse(explicitZone ? value : `${value.replace(" ", "T")}Z`);
|
|
252474
252534
|
return Number.isNaN(parsed) ? null : parsed;
|
|
252475
252535
|
};
|
|
252476
|
-
var
|
|
252536
|
+
var mergeBy = (local, remote, limit, rank) => {
|
|
252477
252537
|
const byId = /* @__PURE__ */ new Map();
|
|
252478
252538
|
for (const row of [...local, ...remote]) if (!byId.has(row.id)) byId.set(row.id, row);
|
|
252479
|
-
return [...byId.values()].sort((left, right) => (right
|
|
252539
|
+
return [...byId.values()].sort((left, right) => rank(right) - rank(left) || left.id.localeCompare(right.id)).slice(0, limit);
|
|
252480
252540
|
};
|
|
252541
|
+
var mergeActivity = (local, remote, limit) => mergeBy(local, remote, limit, (row) => row.lastActiveAt ?? 0);
|
|
252481
252542
|
async function getProjectActivity(storage2, projectId, userId) {
|
|
252482
252543
|
const [
|
|
252483
252544
|
recentThreads,
|
|
@@ -252487,6 +252548,8 @@ async function getProjectActivity(storage2, projectId, userId) {
|
|
|
252487
252548
|
priorityTasks,
|
|
252488
252549
|
localAttentionSessions,
|
|
252489
252550
|
remoteAttentionSessions,
|
|
252551
|
+
localStarredSessions,
|
|
252552
|
+
remoteStarredSessions,
|
|
252490
252553
|
attentionRuns,
|
|
252491
252554
|
runningSessions,
|
|
252492
252555
|
runningRuns,
|
|
@@ -252500,6 +252563,8 @@ async function getProjectActivity(storage2, projectId, userId) {
|
|
|
252500
252563
|
storage2.tasks.listPriorityByProject(projectId, PRIORITY_TASK_LIMIT),
|
|
252501
252564
|
storage2.agentSessions.listAttentionActivityByProject(projectId, ATTENTION_LIMIT, "project-activity"),
|
|
252502
252565
|
storage2.searchCache.listRemoteSessionAttentionByProject(projectId, ATTENTION_LIMIT, "project-activity"),
|
|
252566
|
+
storage2.agentSessions.listFavoritedActivityByProject(projectId, STARRED_LIMIT + 1, "project-activity"),
|
|
252567
|
+
storage2.searchCache.listRemoteSessionFavoritesByProject(projectId, STARRED_LIMIT + 1, "project-activity"),
|
|
252503
252568
|
storage2.scheduledTaskRuns.getAttentionByProject(projectId, ATTENTION_LIMIT),
|
|
252504
252569
|
storage2.agentSessions.countRunningActivityByProject(projectId),
|
|
252505
252570
|
storage2.scheduledTaskRuns.countByProjectStatuses(projectId, ["starting", "running"]),
|
|
@@ -252516,6 +252581,14 @@ async function getProjectActivity(storage2, projectId, userId) {
|
|
|
252516
252581
|
remoteAttentionSessions,
|
|
252517
252582
|
ATTENTION_LIMIT
|
|
252518
252583
|
);
|
|
252584
|
+
const starredProbe = mergeBy(
|
|
252585
|
+
localStarredSessions,
|
|
252586
|
+
remoteStarredSessions,
|
|
252587
|
+
STARRED_LIMIT + 1,
|
|
252588
|
+
(row) => row.favoritedAt ?? 0
|
|
252589
|
+
);
|
|
252590
|
+
const starredHasMore = starredProbe.length > STARRED_LIMIT;
|
|
252591
|
+
const starredSessions = starredProbe.slice(0, STARRED_LIMIT);
|
|
252519
252592
|
const attention = [
|
|
252520
252593
|
...attentionSessions.map((session) => ({
|
|
252521
252594
|
type: "agent_session",
|
|
@@ -252540,6 +252613,8 @@ async function getProjectActivity(storage2, projectId, userId) {
|
|
|
252540
252613
|
return {
|
|
252541
252614
|
recentThreads,
|
|
252542
252615
|
recentAgentSessions,
|
|
252616
|
+
starredSessions,
|
|
252617
|
+
starredHasMore,
|
|
252543
252618
|
recentScheduleRuns,
|
|
252544
252619
|
priorityTasks,
|
|
252545
252620
|
attention,
|
|
@@ -263527,6 +263602,10 @@ var MACHINE_KEY_SETTING = "reverse_machine_private_key";
|
|
|
263527
263602
|
var RECONNECT_BASE_DELAY_MS = 1e3;
|
|
263528
263603
|
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
263529
263604
|
var NO_PING_TIMEOUT_MS = 4e4;
|
|
263605
|
+
var HANDSHAKE_TIMEOUT_MS = 2e4;
|
|
263606
|
+
var SUPERVISOR_INTERVAL_MS = 1e4;
|
|
263607
|
+
var DIAL_TIMEOUT_MS = 25e3;
|
|
263608
|
+
var CLOSING_TIMEOUT_MS = 1e4;
|
|
263530
263609
|
var AUTH_REJECT_CODES = /* @__PURE__ */ new Set([4001, 4003]);
|
|
263531
263610
|
var HEALTHY_CONNECTION_MS = 5e3;
|
|
263532
263611
|
function isTextualContentType(contentType2) {
|
|
@@ -263544,7 +263623,13 @@ var ReverseConnectClient = class {
|
|
|
263544
263623
|
reconnectAttempt = 0;
|
|
263545
263624
|
openedAt = null;
|
|
263546
263625
|
reconnectTimer = null;
|
|
263547
|
-
|
|
263626
|
+
supervisorTimer = null;
|
|
263627
|
+
/** When the current dial started — the CONNECTING watchdog's reference point. */
|
|
263628
|
+
dialStartedAt = 0;
|
|
263629
|
+
/** Last time the hub said anything on the control socket (a superset of pings). */
|
|
263630
|
+
lastFrameAt = 0;
|
|
263631
|
+
/** First tick that observed CLOSING, so a stalled teardown can be forced. */
|
|
263632
|
+
closingSince = 0;
|
|
263548
263633
|
shuttingDown = false;
|
|
263549
263634
|
constructor(localServer, serverUrl, token, localPort) {
|
|
263550
263635
|
this.localServer = localServer;
|
|
@@ -263554,27 +263639,42 @@ var ReverseConnectClient = class {
|
|
|
263554
263639
|
}
|
|
263555
263640
|
connect() {
|
|
263556
263641
|
if (this.shuttingDown) return;
|
|
263642
|
+
this.startSupervisor();
|
|
263643
|
+
if (this.ws) {
|
|
263644
|
+
console.warn("[ReverseClient] connect() called with a live socket \u2014 discarding the old one");
|
|
263645
|
+
const stale = this.ws;
|
|
263646
|
+
this.ws = null;
|
|
263647
|
+
try {
|
|
263648
|
+
stale.terminate();
|
|
263649
|
+
} catch {
|
|
263650
|
+
}
|
|
263651
|
+
}
|
|
263557
263652
|
const cleanUrl = this.serverUrl.replace(/\/+$/, "");
|
|
263558
263653
|
const wsProtocol = cleanUrl.startsWith("https") ? "wss" : "ws";
|
|
263559
263654
|
const wsUrl = cleanUrl.replace(/^https?/, wsProtocol);
|
|
263560
263655
|
const connectUrl = `${wsUrl}/api/reverse-connect?token=${encodeURIComponent(this.token)}`;
|
|
263561
263656
|
console.log(`[ReverseClient] Connecting to ${cleanUrl}...`);
|
|
263562
|
-
this.
|
|
263563
|
-
|
|
263564
|
-
|
|
263565
|
-
|
|
263657
|
+
this.dialStartedAt = Date.now();
|
|
263658
|
+
this.closingSince = 0;
|
|
263659
|
+
const ws = new wrapper_default(connectUrl, {
|
|
263660
|
+
maxPayload: 11 * 1024 * 1024,
|
|
263661
|
+
handshakeTimeout: HANDSHAKE_TIMEOUT_MS
|
|
263662
|
+
});
|
|
263663
|
+
this.ws = ws;
|
|
263664
|
+
ws.on("open", () => {
|
|
263566
263665
|
console.log("[ReverseClient] Socket open, awaiting server handshake");
|
|
263567
263666
|
this.openedAt = Date.now();
|
|
263568
|
-
this.
|
|
263667
|
+
this.lastFrameAt = Date.now();
|
|
263569
263668
|
const frame = {
|
|
263570
263669
|
type: "status",
|
|
263571
263670
|
ready: true,
|
|
263572
263671
|
version: readPackageVersion(),
|
|
263573
263672
|
capabilities: WORKER_CAPABILITY_KEYS
|
|
263574
263673
|
};
|
|
263575
|
-
|
|
263674
|
+
ws.send(JSON.stringify(frame));
|
|
263576
263675
|
});
|
|
263577
|
-
|
|
263676
|
+
ws.on("message", (data) => {
|
|
263677
|
+
this.lastFrameAt = Date.now();
|
|
263578
263678
|
try {
|
|
263579
263679
|
const frame = JSON.parse(data.toString());
|
|
263580
263680
|
this.handleFrame(frame);
|
|
@@ -263582,48 +263682,127 @@ var ReverseConnectClient = class {
|
|
|
263582
263682
|
console.error("[ReverseClient] Failed to parse frame:", err);
|
|
263583
263683
|
}
|
|
263584
263684
|
});
|
|
263585
|
-
|
|
263685
|
+
ws.on("close", (code, reason) => {
|
|
263586
263686
|
const safeReason = redactSecretForms(reason?.toString() || "", this.token);
|
|
263587
|
-
|
|
263588
|
-
const uptime = this.openedAt === null ? 0 : Date.now() - this.openedAt;
|
|
263589
|
-
if (rejected) {
|
|
263590
|
-
console.error(
|
|
263591
|
-
`[ReverseClient] Server rejected this connection (code=${code}, reason=${safeReason}). ` + (code === 4001 ? "The connect token is no longer valid \u2014 open Settings \u2192 Remote Servers, read the current token, and re-run `vibedeckx connect` with it." : "This machine's identity was refused \u2014 the remote record may belong to another machine or another account.") + " Retrying with backoff, but it will not recover on its own."
|
|
263592
|
-
);
|
|
263593
|
-
} else {
|
|
263594
|
-
console.log(`[ReverseClient] Disconnected (code=${code}, reason=${safeReason})`);
|
|
263595
|
-
}
|
|
263596
|
-
if (!rejected && this.openedAt !== null && uptime >= HEALTHY_CONNECTION_MS) {
|
|
263597
|
-
this.reconnectAttempt = 0;
|
|
263598
|
-
}
|
|
263599
|
-
this.openedAt = null;
|
|
263600
|
-
this.clearNoPingTimer();
|
|
263601
|
-
this.closeAllLocalChannels();
|
|
263602
|
-
void this.localServer.remoteMcpSessionManager?.closeAll("reverse-connect disconnected");
|
|
263603
|
-
this.ws = null;
|
|
263604
|
-
if (!this.shuttingDown) {
|
|
263605
|
-
this.scheduleReconnect();
|
|
263606
|
-
}
|
|
263687
|
+
this.handleDisconnect(ws, `code=${code}, reason=${safeReason}`, code);
|
|
263607
263688
|
});
|
|
263608
|
-
|
|
263689
|
+
ws.on("error", (err) => {
|
|
263690
|
+
if (ws !== this.ws) return;
|
|
263609
263691
|
console.error(
|
|
263610
263692
|
"[ReverseClient] WebSocket error:",
|
|
263611
263693
|
redactErrorSecret(err, this.token)
|
|
263612
263694
|
);
|
|
263613
263695
|
});
|
|
263614
263696
|
}
|
|
263697
|
+
/**
|
|
263698
|
+
* The single "this connection is over" funnel. Reached from the close event,
|
|
263699
|
+
* from the supervisor declaring a socket dead, or from both in that order —
|
|
263700
|
+
* so it keys on socket identity and schedules at most one reconnect per
|
|
263701
|
+
* connection.
|
|
263702
|
+
*/
|
|
263703
|
+
handleDisconnect(ws, reason, code) {
|
|
263704
|
+
if (ws !== null && ws !== this.ws) return;
|
|
263705
|
+
const rejected = code !== void 0 && AUTH_REJECT_CODES.has(code);
|
|
263706
|
+
const uptime = this.openedAt === null ? 0 : Date.now() - this.openedAt;
|
|
263707
|
+
if (rejected) {
|
|
263708
|
+
console.error(
|
|
263709
|
+
`[ReverseClient] Server rejected this connection (${reason}). ` + (code === 4001 ? "The connect token is no longer valid \u2014 open Settings \u2192 Remote Servers, read the current token, and re-run `vibedeckx connect` with it." : "This machine's identity was refused \u2014 the remote record may belong to another machine or another account.") + " Retrying with backoff, but it will not recover on its own."
|
|
263710
|
+
);
|
|
263711
|
+
} else {
|
|
263712
|
+
console.log(`[ReverseClient] Disconnected (${reason})`);
|
|
263713
|
+
}
|
|
263714
|
+
if (!rejected && this.openedAt !== null && uptime >= HEALTHY_CONNECTION_MS) {
|
|
263715
|
+
this.reconnectAttempt = 0;
|
|
263716
|
+
}
|
|
263717
|
+
this.openedAt = null;
|
|
263718
|
+
this.closingSince = 0;
|
|
263719
|
+
this.closeAllLocalChannels();
|
|
263720
|
+
void this.localServer.remoteMcpSessionManager?.closeAll("reverse-connect disconnected");
|
|
263721
|
+
this.ws = null;
|
|
263722
|
+
if (ws) {
|
|
263723
|
+
try {
|
|
263724
|
+
ws.terminate();
|
|
263725
|
+
} catch {
|
|
263726
|
+
}
|
|
263727
|
+
}
|
|
263728
|
+
if (this.shuttingDown) return;
|
|
263729
|
+
if (this.reconnectTimer) return;
|
|
263730
|
+
this.scheduleReconnect();
|
|
263731
|
+
}
|
|
263732
|
+
startSupervisor() {
|
|
263733
|
+
if (this.supervisorTimer) return;
|
|
263734
|
+
this.supervisorTimer = setInterval(() => this.superviseTick(), SUPERVISOR_INTERVAL_MS);
|
|
263735
|
+
this.supervisorTimer.unref?.();
|
|
263736
|
+
}
|
|
263737
|
+
stopSupervisor() {
|
|
263738
|
+
if (!this.supervisorTimer) return;
|
|
263739
|
+
clearInterval(this.supervisorTimer);
|
|
263740
|
+
this.supervisorTimer = null;
|
|
263741
|
+
}
|
|
263742
|
+
/**
|
|
263743
|
+
* Level-triggered recovery: judges the current state against the wall clock
|
|
263744
|
+
* rather than waiting to be armed by an event. That is what survives a dial
|
|
263745
|
+
* that never completes, a close frame the peer never answers, and a host that
|
|
263746
|
+
* was suspended for hours.
|
|
263747
|
+
*/
|
|
263748
|
+
superviseTick() {
|
|
263749
|
+
if (this.shuttingDown) return;
|
|
263750
|
+
const now3 = Date.now();
|
|
263751
|
+
const ws = this.ws;
|
|
263752
|
+
if (ws === null) {
|
|
263753
|
+
if (!this.reconnectTimer) {
|
|
263754
|
+
console.warn("[ReverseClient] Supervisor: no socket and no pending reconnect \u2014 self-healing");
|
|
263755
|
+
this.scheduleReconnect();
|
|
263756
|
+
}
|
|
263757
|
+
return;
|
|
263758
|
+
}
|
|
263759
|
+
switch (ws.readyState) {
|
|
263760
|
+
case wrapper_default.CONNECTING: {
|
|
263761
|
+
const stuckFor = now3 - this.dialStartedAt;
|
|
263762
|
+
if (stuckFor > DIAL_TIMEOUT_MS) {
|
|
263763
|
+
console.warn(
|
|
263764
|
+
`[ReverseClient] Supervisor: dial stuck ${Math.round(stuckFor / 1e3)}s with no handshake \u2014 terminating`
|
|
263765
|
+
);
|
|
263766
|
+
this.handleDisconnect(ws, "dial timeout");
|
|
263767
|
+
}
|
|
263768
|
+
break;
|
|
263769
|
+
}
|
|
263770
|
+
case wrapper_default.OPEN: {
|
|
263771
|
+
const silentFor = now3 - this.lastFrameAt;
|
|
263772
|
+
if (silentFor > NO_PING_TIMEOUT_MS) {
|
|
263773
|
+
console.warn(
|
|
263774
|
+
`[ReverseClient] Supervisor: no hub traffic for ${Math.round(silentFor / 1e3)}s \u2014 terminating`
|
|
263775
|
+
);
|
|
263776
|
+
this.handleDisconnect(ws, "no ping timeout");
|
|
263777
|
+
}
|
|
263778
|
+
break;
|
|
263779
|
+
}
|
|
263780
|
+
case wrapper_default.CLOSING: {
|
|
263781
|
+
if (this.closingSince === 0) {
|
|
263782
|
+
this.closingSince = now3;
|
|
263783
|
+
} else if (now3 - this.closingSince > CLOSING_TIMEOUT_MS) {
|
|
263784
|
+
console.warn("[ReverseClient] Supervisor: close handshake stalled \u2014 terminating");
|
|
263785
|
+
this.handleDisconnect(ws, "closing stalled");
|
|
263786
|
+
}
|
|
263787
|
+
break;
|
|
263788
|
+
}
|
|
263789
|
+
default:
|
|
263790
|
+
break;
|
|
263791
|
+
}
|
|
263792
|
+
}
|
|
263615
263793
|
shutdown() {
|
|
263616
263794
|
this.shuttingDown = true;
|
|
263617
|
-
this.
|
|
263795
|
+
this.stopSupervisor();
|
|
263618
263796
|
if (this.reconnectTimer) {
|
|
263619
263797
|
clearTimeout(this.reconnectTimer);
|
|
263620
263798
|
this.reconnectTimer = null;
|
|
263621
263799
|
}
|
|
263622
263800
|
this.closeAllLocalChannels();
|
|
263623
|
-
|
|
263624
|
-
this.ws.close(1e3, "Shutdown");
|
|
263625
|
-
}
|
|
263801
|
+
const ws = this.ws;
|
|
263626
263802
|
this.ws = null;
|
|
263803
|
+
if (ws && (ws.readyState === wrapper_default.OPEN || ws.readyState === wrapper_default.CONNECTING)) {
|
|
263804
|
+
ws.close(1e3, "Shutdown");
|
|
263805
|
+
}
|
|
263627
263806
|
}
|
|
263628
263807
|
async handleFrame(frame) {
|
|
263629
263808
|
switch (frame.type) {
|
|
@@ -263796,7 +263975,6 @@ var ReverseConnectClient = class {
|
|
|
263796
263975
|
}
|
|
263797
263976
|
}
|
|
263798
263977
|
handlePing(frame) {
|
|
263799
|
-
this.resetNoPingTimer();
|
|
263800
263978
|
const pong = { type: "pong", ts: frame.ts };
|
|
263801
263979
|
this.sendFrame(pong);
|
|
263802
263980
|
}
|
|
@@ -263819,21 +263997,6 @@ var ReverseConnectClient = class {
|
|
|
263819
263997
|
this.connect();
|
|
263820
263998
|
}, totalDelay);
|
|
263821
263999
|
}
|
|
263822
|
-
resetNoPingTimer() {
|
|
263823
|
-
this.clearNoPingTimer();
|
|
263824
|
-
this.noPingTimer = setTimeout(() => {
|
|
263825
|
-
console.log(`[ReverseClient] No ping received in ${NO_PING_TIMEOUT_MS / 1e3}s, reconnecting...`);
|
|
263826
|
-
if (this.ws) {
|
|
263827
|
-
this.ws.close(1e3, "No ping timeout");
|
|
263828
|
-
}
|
|
263829
|
-
}, NO_PING_TIMEOUT_MS);
|
|
263830
|
-
}
|
|
263831
|
-
clearNoPingTimer() {
|
|
263832
|
-
if (this.noPingTimer) {
|
|
263833
|
-
clearTimeout(this.noPingTimer);
|
|
263834
|
-
this.noPingTimer = null;
|
|
263835
|
-
}
|
|
263836
|
-
}
|
|
263837
264000
|
closeAllLocalChannels() {
|
|
263838
264001
|
for (const [id, ws] of this.localChannels) {
|
|
263839
264002
|
try {
|