@vibedeckx/linux-x64 0.3.1 → 0.3.2

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.
Files changed (2) hide show
  1. package/dist/bin.js +181 -66
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -186618,20 +186618,6 @@ var createAgentSessionRepos = (kdb, h) => ({
186618
186618
  const row = await kdb.selectFrom("agent_sessions").select(kdb.fn.countAll().as("count")).where("project_id", "=", projectId).where("status", "=", "running").executeTakeFirstOrThrow();
186619
186619
  return Number(row.count);
186620
186620
  },
186621
- countAttentionByProject: async (projectId) => {
186622
- const row = await kdb.selectFrom("agent_sessions").select(kdb.fn.countAll().as("count")).where("project_id", "=", projectId).where((eb) => eb.or([
186623
- eb("status", "=", "error"),
186624
- eb.and([
186625
- eb("status", "=", "stopped"),
186626
- eb("last_user_message_at", "is not", null),
186627
- eb.or([
186628
- eb("last_completed_at", "is", null),
186629
- eb("last_completed_at", "<", eb.ref("last_user_message_at"))
186630
- ])
186631
- ])
186632
- ])).executeTakeFirstOrThrow();
186633
- return Number(row.count);
186634
- },
186635
186621
  getByBranch: async (projectId, branch) => {
186636
186622
  const row = await kdb.selectFrom("agent_sessions").selectAll().where("project_id", "=", projectId).where("branch", "=", branch).orderBy("updated_at", "desc").limit(1).executeTakeFirst();
186637
186623
  return row ? mapAgentSession(row) : void 0;
@@ -187159,14 +187145,9 @@ var createSearchCacheRepos = (kdb, _h) => ({
187159
187145
  },
187160
187146
  countRemoteSessionActivityByProject: async (projectId) => {
187161
187147
  const row = await remoteSessionScope(kdb, projectId).select([
187162
- sql`coalesce(sum(case when c.status = 'running' then 1 else 0 end), 0)`.as("running"),
187163
- sql`coalesce(sum(case
187164
- when c.status = 'error' then 1
187165
- when c.status = 'stopped' and c.last_user_message_at is not null
187166
- and (c.last_completed_at is null or c.last_completed_at < c.last_user_message_at) then 1
187167
- else 0 end), 0)`.as("failed")
187148
+ sql`coalesce(sum(case when c.status = 'running' then 1 else 0 end), 0)`.as("running")
187168
187149
  ]).executeTakeFirstOrThrow();
187169
- return { running: Number(row.running), failed: Number(row.failed) };
187150
+ return { running: Number(row.running) };
187170
187151
  },
187171
187152
  updateRemoteSessionActivity: async (entry) => {
187172
187153
  return kdb.transaction().execute(async (trx) => {
@@ -234438,7 +234419,8 @@ var ProjectChatManager = class {
234438
234419
  this.startupReconciliationDeadlineMs = Math.max(10, options.startupReconciliationDeadlineMs ?? 1e3);
234439
234420
  this.reconciliationOperationTimeoutMs = Math.max(10, options.reconciliationOperationTimeoutMs ?? 250);
234440
234421
  this.recoveryPageSize = Math.max(1, Math.min(100, options.recoveryPageSize ?? 25));
234441
- this.maxConcurrentTurns = Math.max(1, options.maxConcurrentTurns ?? 4);
234422
+ this.maxConcurrentTurns = Math.max(1, options.maxConcurrentTurns ?? 50);
234423
+ this.maxConcurrentTurnsPerUser = Math.max(1, options.maxConcurrentTurnsPerUser ?? 4);
234442
234424
  this.reconciliationDelayMs = Math.min(100, this.reconciliationIntervalMs);
234443
234425
  this.toolDependencies = options.toolDependencies;
234444
234426
  this.unsubscribeEvents = options.eventBus?.subscribe((event) => {
@@ -234478,6 +234460,7 @@ var ProjectChatManager = class {
234478
234460
  reconciliationOperationTimeoutMs;
234479
234461
  recoveryPageSize;
234480
234462
  maxConcurrentTurns;
234463
+ maxConcurrentTurnsPerUser;
234481
234464
  toolDependencies;
234482
234465
  unsubscribeEvents;
234483
234466
  startupReconciliation;
@@ -234488,6 +234471,7 @@ var ProjectChatManager = class {
234488
234471
  pendingPumps = [];
234489
234472
  pendingPumpIds = /* @__PURE__ */ new Set();
234490
234473
  activeTurnCount = 0;
234474
+ activeTurnsByUser = /* @__PURE__ */ new Map();
234491
234475
  reconciliationTimer = null;
234492
234476
  reconciliationDelayMs = 100;
234493
234477
  shuttingDown = false;
@@ -235361,6 +235345,7 @@ var ProjectChatManager = class {
235361
235345
  pendingApprovals: /* @__PURE__ */ new Map(),
235362
235346
  writeTail: Promise.resolve(),
235363
235347
  evictionTimer: null,
235348
+ queuedAt: null,
235364
235349
  contextRefreshGeneration: 0,
235365
235350
  contextRefreshFlight: null,
235366
235351
  contextRefreshBroadcast: false
@@ -235482,22 +235467,51 @@ var ProjectChatManager = class {
235482
235467
  pump(live) {
235483
235468
  if (live.activeWork || this.shuttingDown || this.closingThreads.has(live.thread.id)) return;
235484
235469
  if (live.queue.length === 0) return;
235485
- if (this.activeTurnCount >= this.maxConcurrentTurns) {
235486
- if (!this.pendingPumpIds.has(live.thread.id)) {
235487
- this.pendingPumpIds.add(live.thread.id);
235488
- this.pendingPumps.push(live);
235489
- }
235470
+ if (!this.canStartTurn(live)) {
235471
+ this.enqueuePump(live);
235490
235472
  return;
235491
235473
  }
235492
235474
  this.startPump(live);
235493
235475
  }
235476
+ /**
235477
+ * Park a thread that has work but no slot. Queueing is invisible to the
235478
+ * caller and only shows up as latency, so every entry into this state is
235479
+ * logged once with the cap that caused it.
235480
+ */
235481
+ enqueuePump(live) {
235482
+ if (!this.pendingPumpIds.has(live.thread.id)) {
235483
+ this.pendingPumpIds.add(live.thread.id);
235484
+ this.pendingPumps.push(live);
235485
+ }
235486
+ if (live.queuedAt === null) {
235487
+ live.queuedAt = Date.now();
235488
+ const reason = this.activeTurnCount >= this.maxConcurrentTurns ? "global" : "per-user";
235489
+ console.warn(`[ProjectChat] turn queued (thread=${live.thread.id} user=${live.thread.user_id} reason=${reason} active=${this.activeTurnCount}/${this.maxConcurrentTurns} userActive=${this.activeTurnsByUser.get(live.thread.user_id) ?? 0}/${this.maxConcurrentTurnsPerUser} waiting=${this.pendingPumps.length})`);
235490
+ }
235491
+ if (live.status !== "queued") {
235492
+ live.status = "queued";
235493
+ this.broadcastStatus(live);
235494
+ }
235495
+ }
235496
+ /** A turn may start only when both the global and the per-user cap allow it. */
235497
+ canStartTurn(live) {
235498
+ if (this.activeTurnCount >= this.maxConcurrentTurns) return false;
235499
+ const active = this.activeTurnsByUser.get(live.thread.user_id) ?? 0;
235500
+ return active < this.maxConcurrentTurnsPerUser;
235501
+ }
235494
235502
  startPump(live) {
235495
235503
  if (live.activeWork || this.shuttingDown || this.closingThreads.has(live.thread.id)) return;
235496
235504
  const queued = live.queue.shift();
235497
235505
  if (!queued) return;
235498
235506
  this.pendingPumpIds.delete(live.thread.id);
235499
- const slot = { released: false };
235507
+ const userId = live.thread.user_id;
235508
+ if (live.queuedAt !== null) {
235509
+ console.warn(`[ProjectChat] queued turn started after ${Date.now() - live.queuedAt}ms (thread=${live.thread.id} user=${userId})`);
235510
+ live.queuedAt = null;
235511
+ }
235512
+ const slot = { released: false, userId };
235500
235513
  this.activeTurnCount++;
235514
+ this.activeTurnsByUser.set(userId, (this.activeTurnsByUser.get(userId) ?? 0) + 1);
235501
235515
  live.activeSlot = slot;
235502
235516
  this.cancelEviction(live);
235503
235517
  let work;
@@ -235510,10 +235524,7 @@ var ProjectChatManager = class {
235510
235524
  live.abortController = null;
235511
235525
  if (live.queue.length > 0 && !this.shuttingDown && !this.closingThreads.has(live.thread.id)) {
235512
235526
  if (this.pendingPumps.length > 0) {
235513
- if (!this.pendingPumpIds.has(live.thread.id)) {
235514
- this.pendingPumpIds.add(live.thread.id);
235515
- this.pendingPumps.push(live);
235516
- }
235527
+ this.enqueuePump(live);
235517
235528
  } else {
235518
235529
  this.pump(live);
235519
235530
  }
@@ -235531,14 +235542,23 @@ var ProjectChatManager = class {
235531
235542
  if (slot.released) return;
235532
235543
  slot.released = true;
235533
235544
  this.activeTurnCount = Math.max(0, this.activeTurnCount - 1);
235545
+ const remaining = (this.activeTurnsByUser.get(slot.userId) ?? 0) - 1;
235546
+ if (remaining > 0) this.activeTurnsByUser.set(slot.userId, remaining);
235547
+ else this.activeTurnsByUser.delete(slot.userId);
235534
235548
  this.drainPendingPumps();
235535
235549
  }
235536
235550
  drainPendingPumps() {
235551
+ for (let index = this.pendingPumps.length - 1; index >= 0; index -= 1) {
235552
+ const live = this.pendingPumps[index];
235553
+ if (!live.activeWork && live.queue.length > 0 && !this.closingThreads.has(live.thread.id) && this.liveThreads.get(live.thread.id) === live) continue;
235554
+ this.pendingPumps.splice(index, 1);
235555
+ this.pendingPumpIds.delete(live.thread.id);
235556
+ }
235537
235557
  while (!this.shuttingDown && this.activeTurnCount < this.maxConcurrentTurns) {
235538
- const live = this.pendingPumps.shift();
235539
- if (!live) return;
235558
+ const index = this.pendingPumps.findIndex((live2) => this.canStartTurn(live2));
235559
+ if (index < 0) return;
235560
+ const [live] = this.pendingPumps.splice(index, 1);
235540
235561
  this.pendingPumpIds.delete(live.thread.id);
235541
- if (live.activeWork || live.queue.length === 0 || this.closingThreads.has(live.thread.id) || this.liveThreads.get(live.thread.id) !== live) continue;
235542
235562
  this.startPump(live);
235543
235563
  }
235544
235564
  }
@@ -238823,23 +238843,34 @@ var routes3 = async (fastify2) => {
238823
238843
  const host = request.headers["x-forwarded-host"] || request.headers.host || "localhost";
238824
238844
  return `npx vibedeckx@latest connect --connect-to ${proto}://${host} --token ${token}`;
238825
238845
  };
238826
- for (const mode of ["generate", "rotate"]) {
238827
- fastify2.post(
238828
- `/api/remote-servers/:id/${mode}-token`,
238829
- async (request, reply) => {
238830
- const userId = requireUserFacingUserId(request, reply);
238831
- if (userId === null) return;
238832
- const { id } = request.params;
238833
- const server = await fastify2.storage.remoteServers.getById(id, userId);
238834
- if (!server)
238835
- return reply.code(404).send({ error: "Server not found" });
238836
- const token = mode === "generate" ? await fastify2.storage.remoteServers.generateToken(id, userId) : await fastify2.storage.remoteServers.rotateToken(id, userId);
238837
- if (!token)
238838
- return reply.code(500).send({ error: `Failed to ${mode} token` });
238839
- return reply.send({ token, connectCommand: connectCommandFor(request, token) });
238840
- }
238841
- );
238842
- }
238846
+ const sendConnectToken = async (request, reply, op, failure) => {
238847
+ const userId = requireUserFacingUserId(request, reply);
238848
+ if (userId === null) return;
238849
+ const { id } = request.params;
238850
+ const server = await fastify2.storage.remoteServers.getById(id, userId);
238851
+ if (!server) return reply.code(404).send({ error: "Server not found" });
238852
+ const token = await op(id, userId);
238853
+ if (!token) return reply.code(500).send({ error: failure });
238854
+ return reply.send({ token, connectCommand: connectCommandFor(request, token) });
238855
+ };
238856
+ fastify2.post(
238857
+ "/api/remote-servers/:id/connect-token",
238858
+ (request, reply) => sendConnectToken(
238859
+ request,
238860
+ reply,
238861
+ (id, userId) => fastify2.storage.remoteServers.generateToken(id, userId),
238862
+ "Failed to read token"
238863
+ )
238864
+ );
238865
+ fastify2.post(
238866
+ "/api/remote-servers/:id/connect-token/rotate",
238867
+ (request, reply) => sendConnectToken(
238868
+ request,
238869
+ reply,
238870
+ (id, userId) => fastify2.storage.remoteServers.rotateToken(id, userId),
238871
+ "Failed to rotate token"
238872
+ )
238873
+ );
238843
238874
  fastify2.post(
238844
238875
  "/api/remote-servers/:id/browse",
238845
238876
  async (request, reply) => {
@@ -238891,8 +238922,8 @@ var routes3 = async (fastify2) => {
238891
238922
  }
238892
238923
  }
238893
238924
  );
238894
- fastify2.post(
238895
- "/api/remote-servers/:id/revoke-token",
238925
+ fastify2.delete(
238926
+ "/api/remote-servers/:id/connect-token",
238896
238927
  async (request, reply) => {
238897
238928
  const userId = requireUserFacingUserId(request, reply);
238898
238929
  if (userId === null) return;
@@ -242059,6 +242090,14 @@ var routes11 = async (fastify2) => {
242059
242090
  console.error(`[API] remote activity write-through failed for ${req.params.sessionId}:`, error48);
242060
242091
  return false;
242061
242092
  });
242093
+ ensureRemoteAgentStream(req.params.sessionId, {
242094
+ remoteSessionMap: fastify2.remoteSessionMap,
242095
+ remotePatchCache: fastify2.remotePatchCache,
242096
+ reverseConnectManager: fastify2.reverseConnectManager,
242097
+ eventBus: fastify2.eventBus,
242098
+ agentSessionManager: fastify2.agentSessionManager,
242099
+ storage: fastify2.storage
242100
+ });
242062
242101
  if (activityReady === true) {
242063
242102
  fastify2.agentSessionManager.emitBranchActivityIfChanged(
242064
242103
  projectId,
@@ -243464,7 +243503,7 @@ var project_chat_routes_default = (0, import_fastify_plugin16.default)(routes15,
243464
243503
  var import_fastify_plugin17 = __toESM(require_plugin2(), 1);
243465
243504
 
243466
243505
  // src/project-activity.ts
243467
- var RECENT_THREAD_LIMIT = 3;
243506
+ var RECENT_THREAD_LIMIT = 5;
243468
243507
  var RECENT_SESSION_LIMIT = 8;
243469
243508
  var RECENT_RUN_LIMIT = 5;
243470
243509
  var PRIORITY_TASK_LIMIT = 5;
@@ -243518,8 +243557,6 @@ async function getProjectActivity(storage, projectId, userId) {
243518
243557
  attentionRuns,
243519
243558
  runningSessions,
243520
243559
  runningRuns,
243521
- failedSessions,
243522
- failedRuns,
243523
243560
  remoteCounts,
243524
243561
  nextScheduleAt
243525
243562
  ] = await Promise.all([
@@ -243533,8 +243570,6 @@ async function getProjectActivity(storage, projectId, userId) {
243533
243570
  storage.scheduledTaskRuns.getAttentionByProject(projectId, ATTENTION_LIMIT),
243534
243571
  storage.agentSessions.countRunningByProject(projectId),
243535
243572
  storage.scheduledTaskRuns.countByProjectStatuses(projectId, ["starting", "running"]),
243536
- storage.agentSessions.countAttentionByProject(projectId),
243537
- storage.scheduledTaskRuns.countByProjectStatuses(projectId, ["failed", "timeout"]),
243538
243573
  storage.searchCache.countRemoteSessionActivityByProject(projectId),
243539
243574
  storage.scheduledTasks.getEarliestNextRunAt(projectId)
243540
243575
  ]);
@@ -243577,7 +243612,6 @@ async function getProjectActivity(storage, projectId, userId) {
243577
243612
  attention,
243578
243613
  summary: {
243579
243614
  running: runningSessions + runningRuns + remoteCounts.running,
243580
- failed: failedSessions + failedRuns + remoteCounts.failed,
243581
243615
  nextScheduleAt
243582
243616
  }
243583
243617
  };
@@ -245347,6 +245381,9 @@ async function authenticateWs(authEnabled, query, socket) {
245347
245381
  if (!userId) return reject("Invalid authentication token");
245348
245382
  return { userId, kind: "user" };
245349
245383
  }
245384
+ function processOwnerScope(principal) {
245385
+ return principal.kind === "user" ? principal.userId : null;
245386
+ }
245350
245387
  async function localProcessProjectId(fastify2, processId) {
245351
245388
  const live = fastify2.processManager.getProcessProjectId(processId);
245352
245389
  if (live) return live;
@@ -245462,7 +245499,7 @@ var routes23 = async (fastify2) => {
245462
245499
  console.log(`[WebSocket] Auth rejected for process ${processId}`);
245463
245500
  return;
245464
245501
  }
245465
- const ownerUserId = principal.kind === "api_key" ? null : principal.userId ?? "local";
245502
+ const ownerUserId = processOwnerScope(principal);
245466
245503
  if (ownerUserId !== null && !await userOwnsProcess(fastify2, processId, ownerUserId)) {
245467
245504
  console.log(`[WebSocket] Ownership denied for process ${processId} (user=${ownerUserId})`);
245468
245505
  try {
@@ -245531,7 +245568,7 @@ var routes23 = async (fastify2) => {
245531
245568
  const handleInputMap = /* @__PURE__ */ new Map();
245532
245569
  const subscribeProcess = async (processId) => {
245533
245570
  if (subs.has(processId)) return;
245534
- const ownerUserId = principal.kind === "api_key" ? null : principal.userId ?? "local";
245571
+ const ownerUserId = processOwnerScope(principal);
245535
245572
  if (ownerUserId !== null && !await userOwnsProcess(fastify2, processId, ownerUserId)) {
245536
245573
  console.log(`[ExecutorMux] Ownership denied for process ${processId} (user=${ownerUserId})`);
245537
245574
  try {
@@ -245836,8 +245873,46 @@ var websocket_routes_default = (0, import_fastify_plugin24.default)(routes23, {
245836
245873
  // src/routes/reverse-connect-routes.ts
245837
245874
  var import_fastify_plugin25 = __toESM(require_plugin2(), 1);
245838
245875
  import { randomBytes as randomBytes2, createHash as createHash7, verify as cryptoVerify } from "crypto";
245876
+
245877
+ // src/utils/rate-limited-warn.ts
245878
+ function createRateLimitedWarn(windowMs, maxKeys) {
245879
+ const seen = /* @__PURE__ */ new Map();
245880
+ return function warnRateLimited(key, message) {
245881
+ const now3 = Date.now();
245882
+ const entry = seen.get(key);
245883
+ if (entry && now3 - entry.last < windowMs) {
245884
+ entry.suppressed++;
245885
+ return;
245886
+ }
245887
+ if (!entry && seen.size >= maxKeys) {
245888
+ let oldestKey = null;
245889
+ let oldestAt = Infinity;
245890
+ for (const [k2, v2] of seen) {
245891
+ if (now3 - v2.last > windowMs) {
245892
+ seen.delete(k2);
245893
+ continue;
245894
+ }
245895
+ if (v2.last < oldestAt) {
245896
+ oldestAt = v2.last;
245897
+ oldestKey = k2;
245898
+ }
245899
+ }
245900
+ if (seen.size >= maxKeys && oldestKey !== null) seen.delete(oldestKey);
245901
+ }
245902
+ const suppressed = entry?.suppressed ?? 0;
245903
+ seen.set(key, { last: now3, suppressed: 0 });
245904
+ console.warn(
245905
+ message + (suppressed > 0 ? ` (+${suppressed} more in the last ${Math.round(windowMs / 1e3)}s)` : "")
245906
+ );
245907
+ };
245908
+ }
245909
+
245910
+ // src/routes/reverse-connect-routes.ts
245839
245911
  var MACHINE_HANDSHAKE_TIMEOUT_MS = 5e3;
245912
+ var REJECT_LOG_WINDOW_MS = 3e4;
245913
+ var REJECT_LOG_MAX_KEYS = 500;
245840
245914
  var routes24 = async (fastify2) => {
245915
+ const logRejectedUpgrade = createRateLimitedWarn(REJECT_LOG_WINDOW_MS, REJECT_LOG_MAX_KEYS);
245841
245916
  fastify2.get("/api/reverse-connect/identity", async (req, reply) => {
245842
245917
  const token = req.headers["x-vibedeckx-connect-token"];
245843
245918
  if (typeof token !== "string" || token.length === 0) {
@@ -245856,12 +245931,17 @@ var routes24 = async (fastify2) => {
245856
245931
  async (socket, req) => {
245857
245932
  const token = req.query.token;
245858
245933
  if (!token) {
245934
+ logRejectedUpgrade(`no-token:${req.ip}`, `[ReverseConnect] Rejected upgrade from ${req.ip}: no connect token`);
245859
245935
  socket.send(JSON.stringify({ error: "Token required" }));
245860
245936
  socket.close(4001, "Token required");
245861
245937
  return;
245862
245938
  }
245863
245939
  const server = await fastify2.storage.remoteServers.getByToken(token);
245864
245940
  if (!server) {
245941
+ logRejectedUpgrade(
245942
+ `bad-token:${req.ip}`,
245943
+ `[ReverseConnect] Rejected upgrade from ${req.ip}: connect token not recognized \u2014 the worker needs a current token`
245944
+ );
245865
245945
  socket.send(JSON.stringify({ error: "Invalid token" }));
245866
245946
  socket.close(4001, "Invalid token");
245867
245947
  return;
@@ -245899,6 +245979,7 @@ var routes24 = async (fastify2) => {
245899
245979
  const publicKey = frame.publicKey;
245900
245980
  const signature = frame.signature;
245901
245981
  if (!publicKey || !signature) {
245982
+ logRejectedUpgrade(`malformed-auth:${serverId}`, `[ReverseConnect] Malformed machine auth from ${serverId}`);
245902
245983
  socket.close(4003, "Malformed machine auth");
245903
245984
  return;
245904
245985
  }
@@ -245909,6 +245990,7 @@ var routes24 = async (fastify2) => {
245909
245990
  valid = false;
245910
245991
  }
245911
245992
  if (!valid) {
245993
+ logRejectedUpgrade(`bad-signature:${serverId}`, `[ReverseConnect] Bad machine signature from ${serverId}`);
245912
245994
  socket.close(4003, "Bad machine signature");
245913
245995
  return;
245914
245996
  }
@@ -246013,6 +246095,23 @@ var event_routes_default = (0, import_fastify_plugin26.default)(routes25, { name
246013
246095
  // src/routes/terminal-routes.ts
246014
246096
  var import_fastify_plugin27 = __toESM(require_plugin2(), 1);
246015
246097
  import path13 from "path";
246098
+
246099
+ // src/utils/path-project.ts
246100
+ async function ensurePathProjectId(fastify2, projectPath) {
246101
+ const pseudoProjectId = `path:${projectPath}`;
246102
+ if (await fastify2.storage.projects.getById(pseudoProjectId)) return pseudoProjectId;
246103
+ const existingByPath = await fastify2.storage.projects.getByPath(projectPath);
246104
+ if (existingByPath) return existingByPath.id;
246105
+ const name25 = projectPath.split("/").filter(Boolean).pop() || projectPath;
246106
+ try {
246107
+ await fastify2.storage.projects.create({ id: pseudoProjectId, name: name25, path: projectPath });
246108
+ } catch (err) {
246109
+ if (!(err instanceof Error && err.message.includes("UNIQUE constraint failed"))) throw err;
246110
+ }
246111
+ return pseudoProjectId;
246112
+ }
246113
+
246114
+ // src/routes/terminal-routes.ts
246016
246115
  async function getRemoteConfig5(fastify2, project, remoteServerId) {
246017
246116
  const remotes = await fastify2.storage.projectRemotes.getByProject(project.id);
246018
246117
  const target = remoteServerId ? remotes.find((r) => r.remote_server_id === remoteServerId) : remotes[0];
@@ -246030,7 +246129,8 @@ var routes26 = async (fastify2) => {
246030
246129
  }
246031
246130
  const resolvedPath = resolveWorktreePath(projectPath, branch ?? null);
246032
246131
  try {
246033
- const terminal = fastify2.processManager.startTerminal("remote", resolvedPath);
246132
+ const projectId = await ensurePathProjectId(fastify2, projectPath);
246133
+ const terminal = fastify2.processManager.startTerminal(projectId, resolvedPath, branch ?? null);
246034
246134
  return reply.code(201).send({ terminal: { id: terminal.id, name: terminal.name, cwd: resolvedPath } });
246035
246135
  } catch (error48) {
246036
246136
  console.error(`[terminal-routes] Failed to start terminal in ${resolvedPath}:`, error48);
@@ -249435,6 +249535,8 @@ var MACHINE_KEY_SETTING = "reverse_machine_private_key";
249435
249535
  var RECONNECT_BASE_DELAY_MS = 1e3;
249436
249536
  var RECONNECT_MAX_DELAY_MS = 3e4;
249437
249537
  var NO_PING_TIMEOUT_MS = 6e4;
249538
+ var AUTH_REJECT_CODES = /* @__PURE__ */ new Set([4001, 4003]);
249539
+ var HEALTHY_CONNECTION_MS = 5e3;
249438
249540
  function isTextualContentType(contentType) {
249439
249541
  const t = contentType.toLowerCase();
249440
249542
  if (t === "") return true;
@@ -249448,6 +249550,7 @@ var ReverseConnectClient = class {
249448
249550
  localPort;
249449
249551
  localChannels = /* @__PURE__ */ new Map();
249450
249552
  reconnectAttempt = 0;
249553
+ openedAt = null;
249451
249554
  reconnectTimer = null;
249452
249555
  noPingTimer = null;
249453
249556
  shuttingDown = false;
@@ -249468,8 +249571,8 @@ var ReverseConnectClient = class {
249468
249571
  maxPayload: 11 * 1024 * 1024
249469
249572
  });
249470
249573
  this.ws.on("open", () => {
249471
- console.log("[ReverseClient] Connected to server");
249472
- this.reconnectAttempt = 0;
249574
+ console.log("[ReverseClient] Socket open, awaiting server handshake");
249575
+ this.openedAt = Date.now();
249473
249576
  this.resetNoPingTimer();
249474
249577
  const frame = { type: "status", ready: true };
249475
249578
  this.ws.send(JSON.stringify(frame));
@@ -249484,7 +249587,19 @@ var ReverseConnectClient = class {
249484
249587
  });
249485
249588
  this.ws.on("close", (code, reason) => {
249486
249589
  const safeReason = redactSecretForms(reason?.toString() || "", this.token);
249487
- console.log(`[ReverseClient] Disconnected (code=${code}, reason=${safeReason})`);
249590
+ const rejected = AUTH_REJECT_CODES.has(code);
249591
+ const uptime = this.openedAt === null ? 0 : Date.now() - this.openedAt;
249592
+ if (rejected) {
249593
+ console.error(
249594
+ `[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."
249595
+ );
249596
+ } else {
249597
+ console.log(`[ReverseClient] Disconnected (code=${code}, reason=${safeReason})`);
249598
+ }
249599
+ if (!rejected && this.openedAt !== null && uptime >= HEALTHY_CONNECTION_MS) {
249600
+ this.reconnectAttempt = 0;
249601
+ }
249602
+ this.openedAt = null;
249488
249603
  this.clearNoPingTimer();
249489
249604
  this.closeAllLocalChannels();
249490
249605
  this.ws = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"