@vibedeckx/linux-x64 0.3.3 → 0.3.5

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 CHANGED
@@ -13356,12 +13356,12 @@ var require_semver = __commonJS({
13356
13356
  }
13357
13357
  function replaceHyphen(comp, options) {
13358
13358
  const r = HYPHENRANGE_REGEXP;
13359
- return comp.replace(r, (_3, from, fM, fm, fp35, fpr, fb, to, tM, tm, tp, tpr) => {
13359
+ return comp.replace(r, (_3, from, fM, fm, fp36, fpr, fb, to, tM, tm, tp, tpr) => {
13360
13360
  if (isX(fM)) {
13361
13361
  from = "";
13362
13362
  } else if (isX(fm)) {
13363
13363
  from = `>=${fM}.0.0${options?.includePrerelease ? "-0" : ""}`;
13364
- } else if (isX(fp35)) {
13364
+ } else if (isX(fp36)) {
13365
13365
  from = `>=${fM}.${fm}.0${options?.includePrerelease ? "-0" : ""}`;
13366
13366
  } else if (fpr) {
13367
13367
  from = `>=${from}`;
@@ -87525,12 +87525,12 @@ var require_range = __commonJS({
87525
87525
  debug("replaceGTE0", comp, options);
87526
87526
  return comp.trim().replace(re2[options.includePrerelease ? t.GTE0PRE : t.GTE0], "");
87527
87527
  };
87528
- var hyphenReplace = (incPr) => ($0, from, fM, fm, fp35, fpr, fb, to, tM, tm, tp, tpr) => {
87528
+ var hyphenReplace = (incPr) => ($0, from, fM, fm, fp36, fpr, fb, to, tM, tm, tp, tpr) => {
87529
87529
  if (isX(fM)) {
87530
87530
  from = "";
87531
87531
  } else if (isX(fm)) {
87532
87532
  from = `>=${fM}.0.0${incPr ? "-0" : ""}`;
87533
- } else if (isX(fp35)) {
87533
+ } else if (isX(fp36)) {
87534
87534
  from = `>=${fM}.${fm}.0${incPr ? "-0" : ""}`;
87535
87535
  } else if (fpr) {
87536
87536
  from = `>=${from}`;
@@ -90111,8 +90111,8 @@ var require_pretty_print = __commonJS({
90111
90111
  serializedRoute += serializeMetaData(route.metaData);
90112
90112
  return serializedRoute;
90113
90113
  }
90114
- function mergeSimilarRoutes(routes34) {
90115
- return routes34.reduce((mergedRoutes, route) => {
90114
+ function mergeSimilarRoutes(routes35) {
90115
+ return routes35.reduce((mergedRoutes, route) => {
90116
90116
  for (const nodeRoute of mergedRoutes) {
90117
90117
  if (deepEqual(route.opts.constraints, nodeRoute.opts.constraints) && deepEqual(route.metaData, nodeRoute.metaData)) {
90118
90118
  nodeRoute.method += ", " + route.method;
@@ -90124,18 +90124,18 @@ var require_pretty_print = __commonJS({
90124
90124
  }, []);
90125
90125
  }
90126
90126
  function serializeNode(node, prefix, options) {
90127
- let routes34 = node.routes;
90127
+ let routes35 = node.routes;
90128
90128
  if (options.method === void 0) {
90129
- routes34 = routes34.map(normalizeRoute);
90129
+ routes35 = routes35.map(normalizeRoute);
90130
90130
  }
90131
- routes34 = routes34.map((route) => {
90131
+ routes35 = routes35.map((route) => {
90132
90132
  route.metaData = getRouteMetaData(route, options);
90133
90133
  return route;
90134
90134
  });
90135
90135
  if (options.method === void 0) {
90136
- routes34 = mergeSimilarRoutes(routes34);
90136
+ routes35 = mergeSimilarRoutes(routes35);
90137
90137
  }
90138
- return routes34.map(serializeRoute).join(`
90138
+ return routes35.map(serializeRoute).join(`
90139
90139
  ${prefix}`);
90140
90140
  }
90141
90141
  function buildObjectTree(node, tree, prefix, options) {
@@ -91402,9 +91402,9 @@ var require_find_my_way = __commonJS({
91402
91402
  pathIndex = paramEndIndex;
91403
91403
  }
91404
91404
  };
91405
- Router.prototype._rebuild = function(routes34) {
91405
+ Router.prototype._rebuild = function(routes35) {
91406
91406
  this.reset();
91407
- for (const route of routes34) {
91407
+ for (const route of routes35) {
91408
91408
  const { method, path: path22, opts, handler, store } = route;
91409
91409
  this._on(method, path22, opts, handler, store);
91410
91410
  }
@@ -100612,8 +100612,8 @@ var require_commonjs6 = __commonJS({
100612
100612
  return this.#fullpath = this.name;
100613
100613
  }
100614
100614
  const pv = p2.fullpath();
100615
- const fp35 = pv + (!p2.parent ? "" : this.sep) + name25;
100616
- return this.#fullpath = fp35;
100615
+ const fp36 = pv + (!p2.parent ? "" : this.sep) + name25;
100616
+ return this.#fullpath = fp36;
100617
100617
  }
100618
100618
  /**
100619
100619
  * On platforms other than windows, this is identical to fullpath.
@@ -105329,7 +105329,7 @@ var require_static = __commonJS({
105329
105329
  var { fileURLToPath: fileURLToPath4 } = __require("node:url");
105330
105330
  var { statSync: statSync2 } = __require("node:fs");
105331
105331
  var { glob } = require_commonjs7();
105332
- var fp35 = require_plugin2();
105332
+ var fp36 = require_plugin2();
105333
105333
  var send = require_send2();
105334
105334
  var encodingNegotiator = require_accept_negotiator();
105335
105335
  var contentDisposition = require_content_disposition();
@@ -105433,7 +105433,7 @@ var require_static = __commonJS({
105433
105433
  } else {
105434
105434
  const indexes = new Set(opts.index === void 0 ? ["index.html"] : [].concat(opts.index));
105435
105435
  const indexDirs = /* @__PURE__ */ new Map();
105436
- const routes34 = /* @__PURE__ */ new Set();
105436
+ const routes35 = /* @__PURE__ */ new Set();
105437
105437
  const roots = Array.isArray(sendOptions.root) ? sendOptions.root : [sendOptions.root];
105438
105438
  for (let rootPath of roots) {
105439
105439
  rootPath = rootPath.split(path22.win32.sep).join(path22.posix.sep);
@@ -105449,10 +105449,10 @@ var require_static = __commonJS({
105449
105449
  for (let file2 of files) {
105450
105450
  file2 = file2.split(path22.win32.sep).join(path22.posix.sep);
105451
105451
  const route = prefix + file2;
105452
- if (routes34.has(route)) {
105452
+ if (routes35.has(route)) {
105453
105453
  continue;
105454
105454
  }
105455
- routes34.add(route);
105455
+ routes35.add(route);
105456
105456
  setUpHeadAndGet(routeOpts, route, `/${file2}`, rootPath);
105457
105457
  const key = path22.posix.basename(route);
105458
105458
  if (indexes.has(key) && !indexDirs.has(key)) {
@@ -105747,7 +105747,7 @@ var require_static = __commonJS({
105747
105747
  throw err;
105748
105748
  }
105749
105749
  }
105750
- module.exports = fp35(fastifyStatic2, {
105750
+ module.exports = fp36(fastifyStatic2, {
105751
105751
  fastify: "5.x",
105752
105752
  name: "@fastify/static"
105753
105753
  });
@@ -112390,7 +112390,7 @@ var require_websocket2 = __commonJS({
112390
112390
  var { ServerResponse } = __require("node:http");
112391
112391
  var { PassThrough } = __require("node:stream");
112392
112392
  var { randomBytes: randomBytes3 } = __require("node:crypto");
112393
- var fp35 = require_plugin2();
112393
+ var fp36 = require_plugin2();
112394
112394
  var WebSocket2 = require_ws();
112395
112395
  var Duplexify = require_duplexify();
112396
112396
  var kWs = /* @__PURE__ */ Symbol("ws-socket");
@@ -112576,7 +112576,7 @@ var require_websocket2 = __commonJS({
112576
112576
  }
112577
112577
  next();
112578
112578
  }
112579
- module.exports = fp35(fastifyWebsocket2, {
112579
+ module.exports = fp36(fastifyWebsocket2, {
112580
112580
  fastify: "5.x",
112581
112581
  name: "@fastify/websocket"
112582
112582
  });
@@ -115020,7 +115020,7 @@ var require_multipart2 = __commonJS({
115020
115020
  "use strict";
115021
115021
  var Busboy = require_main();
115022
115022
  var os2 = __require("node:os");
115023
- var fp35 = require_plugin2();
115023
+ var fp36 = require_plugin2();
115024
115024
  var { createWriteStream: createWriteStream2 } = __require("node:fs");
115025
115025
  var { unlink: unlink2 } = __require("node:fs/promises");
115026
115026
  var path22 = __require("node:path");
@@ -115469,7 +115469,7 @@ var require_multipart2 = __commonJS({
115469
115469
  }
115470
115470
  });
115471
115471
  }
115472
- module.exports = fp35(fastifyMultipart2, {
115472
+ module.exports = fp36(fastifyMultipart2, {
115473
115473
  fastify: "5.x",
115474
115474
  name: "@fastify/multipart"
115475
115475
  });
@@ -154378,6 +154378,7 @@ var VIBEDECKX_HOME = path.join(homedir(), ".vibedeckx");
154378
154378
  var DB_PATH = path.join(VIBEDECKX_HOME, "data.sqlite");
154379
154379
  var DEFAULT_PORT = 5173;
154380
154380
  var MIN_WORKER_VERSION = "0.0.0";
154381
+ var WORKER_VERSION_REPORTING_SINCE = "2026-08-03";
154381
154382
 
154382
154383
  // src/load-env.ts
154383
154384
  function getArg(argv, name25) {
@@ -169020,11 +169021,11 @@ function buildCommand(builderArgs) {
169020
169021
  }
169021
169022
  };
169022
169023
  }
169023
- function* generateRouteMapHelpLines(routes34, docs, args) {
169024
+ function* generateRouteMapHelpLines(routes35, docs, args) {
169024
169025
  const { brief, fullDescription, hideRoute } = docs;
169025
169026
  const { headers } = args.text;
169026
169027
  yield args.ansiColor ? `\x1B[1m${headers.usage}\x1B[22m` : headers.usage;
169027
- for (const [name25, route] of Object.entries(routes34)) {
169028
+ for (const [name25, route] of Object.entries(routes35)) {
169028
169029
  if (!hideRoute || !hideRoute[name25] || args.includeHidden) {
169029
169030
  const externalRouteName = args.config.caseStyle === "convert-camel-to-kebab" ? convertCamelCaseToKebabCase(name25) : name25;
169030
169031
  yield ` ${route.formatUsageLine({
@@ -169054,7 +169055,7 @@ function* generateRouteMapHelpLines(routes34, docs, args) {
169054
169055
  }
169055
169056
  yield "";
169056
169057
  yield args.ansiColor ? `\x1B[1m${headers.commands}\x1B[22m` : headers.commands;
169057
- const visibleRoutes = Object.entries(routes34).filter(
169058
+ const visibleRoutes = Object.entries(routes35).filter(
169058
169059
  ([name25]) => !hideRoute || !hideRoute[name25] || args.includeHidden
169059
169060
  );
169060
169061
  const rows = visibleRoutes.map(([internalRouteName, route]) => {
@@ -169082,24 +169083,24 @@ function* generateRouteMapHelpLines(routes34, docs, args) {
169082
169083
  }
169083
169084
  }
169084
169085
  function buildRouteMap({
169085
- routes: routes34,
169086
+ routes: routes35,
169086
169087
  defaultCommand: defaultCommandRoute,
169087
169088
  docs,
169088
169089
  aliases
169089
169090
  }) {
169090
- if (Object.entries(routes34).length === 0) {
169091
+ if (Object.entries(routes35).length === 0) {
169091
169092
  throw new InternalError("Route map must contain at least one route");
169092
169093
  }
169093
169094
  const activeAliases = aliases ?? {};
169094
169095
  const aliasesByRoute = /* @__PURE__ */ new Map();
169095
169096
  for (const [alias, routeName] of Object.entries(activeAliases)) {
169096
- if (alias in routes34) {
169097
+ if (alias in routes35) {
169097
169098
  throw new InternalError(`Cannot use "${alias}" as an alias when a route with that name already exists`);
169098
169099
  }
169099
169100
  const routeAliases = aliasesByRoute.get(routeName) ?? [];
169100
169101
  aliasesByRoute.set(routeName, [...routeAliases, alias]);
169101
169102
  }
169102
- const defaultCommand = defaultCommandRoute ? routes34[defaultCommandRoute] : void 0;
169103
+ const defaultCommand = defaultCommandRoute ? routes35[defaultCommandRoute] : void 0;
169103
169104
  if (defaultCommand && defaultCommand.kind === RouteMapSymbol) {
169104
169105
  throw new InternalError(
169105
169106
  `Cannot use "${defaultCommandRoute}" as the default command because it is not a Command`
@@ -169108,7 +169109,7 @@ function buildRouteMap({
169108
169109
  const resolveRouteName = (input) => {
169109
169110
  if (input in activeAliases) {
169110
169111
  return activeAliases[input];
169111
- } else if (input in routes34) {
169112
+ } else if (input in routes35) {
169112
169113
  return input;
169113
169114
  }
169114
169115
  };
@@ -169126,7 +169127,7 @@ function buildRouteMap({
169126
169127
  return `${args.prefix.join(" ")} ${routeNames.join("|")} ...`;
169127
169128
  },
169128
169129
  formatHelp: (config2) => {
169129
- const lines = [...generateRouteMapHelpLines(routes34, docs, config2)];
169130
+ const lines = [...generateRouteMapHelpLines(routes35, docs, config2)];
169130
169131
  const text2 = lines.join("\n");
169131
169132
  return text2 + "\n";
169132
169133
  },
@@ -169169,11 +169170,11 @@ function buildRouteMap({
169169
169170
  },
169170
169171
  getRoutingTargetForInput: (input) => {
169171
169172
  const routeName = input in activeAliases ? activeAliases[input] : input;
169172
- return routes34[routeName];
169173
+ return routes35[routeName];
169173
169174
  },
169174
169175
  getAllEntries() {
169175
169176
  const hiddenRoutes = docs.hideRoute;
169176
- return Object.entries(routes34).map(([originalRouteName, target]) => {
169177
+ return Object.entries(routes35).map(([originalRouteName, target]) => {
169177
169178
  return {
169178
169179
  name: {
169179
169180
  original: originalRouteName,
@@ -185911,8 +185912,6 @@ var mapProject = (row) => ({
185911
185912
  path: row.path,
185912
185913
  is_remote: fromDbBool(row.is_remote),
185913
185914
  remote_path: row.remote_path ?? void 0,
185914
- remote_url: row.remote_url ?? void 0,
185915
- remote_api_key: row.remote_api_key ?? void 0,
185916
185915
  agent_mode: row.agent_mode ?? "local",
185917
185916
  executor_mode: row.executor_mode ?? "local",
185918
185917
  sync_up_config: row.sync_up_config ? JSON.parse(row.sync_up_config) : void 0,
@@ -185928,10 +185927,10 @@ var createCoreRepos = (kdb, h) => ({
185928
185927
  path: opts.path ?? null,
185929
185928
  remote_path: opts.remote_path ?? null,
185930
185929
  is_remote: h.toDbBool(false),
185931
- remote_url: null,
185932
- remote_api_key: null,
185933
- // Dead column: never populated by any current caller (kept only so
185934
- // legacy DDL/back-compat readers relying on its presence don't break).
185930
+ // Dead columns: never populated by any current caller (kept only so
185931
+ // legacy DDL/back-compat readers relying on their presence don't break).
185932
+ // remote_url/remote_api_key are leftovers from the removed direct-URL
185933
+ // (outbound) transport all remote traffic rides reverse-connect now.
185935
185934
  remote_project_id: null,
185936
185935
  agent_mode: opts.agent_mode ?? "local",
185937
185936
  executor_mode: opts.executor_mode ?? "local",
@@ -186114,9 +186113,6 @@ var createRemoteServerRepos = (kdb, _h) => ({
186114
186113
  await kdb.insertInto("remote_servers").values({
186115
186114
  id,
186116
186115
  name: server.name,
186117
- url: null,
186118
- api_key: null,
186119
- connection_mode: "inbound",
186120
186116
  connect_token: null,
186121
186117
  connect_token_created_at: null,
186122
186118
  last_connected_at: null,
@@ -186339,8 +186335,17 @@ var mapExecutorProcess = (row) => ({
186339
186335
  status: row.status
186340
186336
  });
186341
186337
  var mapRemoteExecutorProcess = (row) => ({
186342
- ...row,
186343
- status: row.status
186338
+ local_process_id: row.local_process_id,
186339
+ remote_server_id: row.remote_server_id,
186340
+ remote_process_id: row.remote_process_id,
186341
+ executor_id: row.executor_id,
186342
+ project_id: row.project_id,
186343
+ branch: row.branch,
186344
+ started_at: row.started_at,
186345
+ status: row.status,
186346
+ exit_code: row.exit_code,
186347
+ finished_at: row.finished_at,
186348
+ machine_id: row.machine_id
186344
186349
  });
186345
186350
  var createExecutorRepos = (kdb, h) => ({
186346
186351
  executorGroups: {
@@ -187170,6 +187175,10 @@ var createSearchCacheRepos = (kdb, _h) => ({
187170
187175
  ]).executeTakeFirstOrThrow();
187171
187176
  return { running: Number(row.running) };
187172
187177
  },
187178
+ countRunningRemoteSessions: async () => {
187179
+ const row = await kdb.selectFrom("session_search_cache as c").innerJoin("remote_session_mappings as mapping", (join2) => join2.onRef("mapping.local_session_id", "=", "c.local_session_id").onRef("mapping.project_id", "=", "c.project_id").onRef("mapping.remote_server_id", "=", "c.target_id")).innerJoin("project_remotes as association", (join2) => join2.onRef("association.project_id", "=", "c.project_id").onRef("association.remote_server_id", "=", "c.target_id")).where("c.target_id", "!=", "local").where("c.deleted_at", "is", null).where("c.status", "=", "running").select([sql`count(*)`.as("n")]).executeTakeFirstOrThrow();
187180
+ return Number(row.n);
187181
+ },
187173
187182
  updateRemoteSessionActivity: async (entry) => {
187174
187183
  return kdb.transaction().execute(async (trx) => {
187175
187184
  const authorized = await trx.selectFrom("remote_session_mappings as mapping").innerJoin("project_remotes as association", (join2) => join2.onRef("association.project_id", "=", "mapping.project_id").onRef("association.remote_server_id", "=", "mapping.remote_server_id")).select("mapping.branch").where("mapping.local_session_id", "=", entry.localSessionId).where("mapping.project_id", "=", entry.projectId).where("mapping.remote_server_id", "=", entry.targetId).where("mapping.remote_session_id", "=", entry.remoteSessionId).executeTakeFirst();
@@ -205171,6 +205180,13 @@ function findTurnOpeningUserEntry(entries, beforeIndex) {
205171
205180
  }
205172
205181
  return opening;
205173
205182
  }
205183
+ function findLatestUserEntry(entries) {
205184
+ for (let i = entries.length - 1; i >= 0; i--) {
205185
+ const entry = entries[i];
205186
+ if (entry?.type === "user") return entry;
205187
+ }
205188
+ return void 0;
205189
+ }
205174
205190
  function resolveNotificationDisposition(openingUserEntry) {
205175
205191
  if (openingUserEntry?.type !== "user") return "result";
205176
205192
  if (openingUserEntry.notificationDisposition) return openingUserEntry.notificationDisposition;
@@ -228160,6 +228176,10 @@ var AgentSessionManager = class {
228160
228176
  if (event.type === "turn_started" || event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request") {
228161
228177
  this.applyCompletionTimerAction(session, session.completion.noteTurnActivity());
228162
228178
  }
228179
+ if (session.turnOpenSince === null && (event.type === "turn_started" || event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request")) {
228180
+ session.turnOpenSince = timestamp;
228181
+ session.turnDisposition = resolveNotificationDisposition(findLatestUserEntry(session.store.entries));
228182
+ }
228163
228183
  if (session.status !== "running" && (event.type === "text" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result" || event.type === "approval_request")) {
228164
228184
  session.status = "running";
228165
228185
  if (!session.skipDb) await this.storage.agentSessions.updateStatus(sessionId, "running");
@@ -230235,6 +230255,26 @@ function tryParseWsMessage(raw) {
230235
230255
  return void 0;
230236
230256
  }
230237
230257
  }
230258
+ function isEntryPatchFrame(parsed) {
230259
+ if (!parsed || !("JsonPatch" in parsed)) return false;
230260
+ const ops = parsed.JsonPatch;
230261
+ if (!Array.isArray(ops)) return false;
230262
+ return ops.some((op) => typeof op?.path === "string" && op.path.startsWith("/entries/"));
230263
+ }
230264
+ function entryPatchFrames(messages) {
230265
+ const frames = [];
230266
+ for (const raw of messages) {
230267
+ if (isEntryPatchFrame(tryParseWsMessage(raw))) frames.push(raw);
230268
+ }
230269
+ return frames;
230270
+ }
230271
+ function isSequencePrefix(cached2, replay) {
230272
+ if (cached2.length > replay.length) return false;
230273
+ for (let i = 0; i < cached2.length; i++) {
230274
+ if (cached2[i] !== replay[i]) return false;
230275
+ }
230276
+ return true;
230277
+ }
230238
230278
  async function persistRemoteSessionActivityFrame(storage, sessionId, remoteInfo, parsed, activityAt = Date.now()) {
230239
230279
  const projectId = projectIdFromRemoteSessionId(sessionId, remoteInfo);
230240
230280
  const statusEvent = statusEventFromRemotePatch(parsed, sessionId, remoteInfo);
@@ -230298,6 +230338,7 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
230298
230338
  const raw = data.toString();
230299
230339
  const parsed = tryParseWsMessage(raw);
230300
230340
  if (!parsed) return;
230341
+ if ("keepalive" in parsed) return;
230301
230342
  const kind = "JsonPatch" in parsed ? "JsonPatch" : "finished" in parsed ? "finished" : "taskCompleted" in parsed ? "taskCompleted" : "workflowRunUpdated" in parsed ? "workflowRunUpdated" : "processAlive" in parsed ? "processAlive" : "branchActivity" in parsed ? "branchActivity" : "Ready" in parsed ? "Ready" : "error" in parsed ? "error" : "other";
230302
230343
  if (kind === "JsonPatch") {
230303
230344
  const ops = parsed.JsonPatch;
@@ -230445,23 +230486,18 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
230445
230486
  if ("Ready" in parsed) {
230446
230487
  syncing = false;
230447
230488
  const currentEntry = cache2.get(sessionId);
230448
- const cachedMsgCount = currentEntry.messages.length;
230449
- if (replayBuffer.length > cachedMsgCount) {
230450
- const delta = replayBuffer.slice(cachedMsgCount);
230451
- console.log(`[AgentWS] Sync delta: ${delta.length} new msgs for ${sessionId}`);
230489
+ const cachedSeq = entryPatchFrames(currentEntry.messages);
230490
+ const extendsCache = isSequencePrefix(cachedSeq, replayBuffer);
230491
+ if (extendsCache && replayBuffer.length > cachedSeq.length) {
230492
+ const delta = replayBuffer.slice(cachedSeq.length);
230493
+ console.log(`[AgentWS] Sync delta: ${delta.length} new entry patches for ${sessionId} (remote=${replayBuffer.length}, cached=${cachedSeq.length})`);
230452
230494
  for (const msg of delta) {
230453
- const p2 = tryParseWsMessage(msg);
230454
- cache2.appendMessage(sessionId, msg, !!(p2 && "JsonPatch" in p2));
230495
+ cache2.appendMessage(sessionId, msg, true);
230455
230496
  cache2.broadcast(sessionId, msg);
230456
230497
  }
230457
- } else if (replayBuffer.length < cachedMsgCount) {
230458
- console.log(`[AgentWS] Sync stale cache for ${sessionId}: remote=${replayBuffer.length}, cached=${cachedMsgCount}`);
230459
- let newPatchCount = 0;
230460
- for (const msg of replayBuffer) {
230461
- const p2 = tryParseWsMessage(msg);
230462
- if (p2 && "JsonPatch" in p2) newPatchCount++;
230463
- }
230464
- cache2.replaceAll(sessionId, [...replayBuffer], newPatchCount);
230498
+ } else if (!extendsCache) {
230499
+ console.log(`[AgentWS] Sync replace for ${sessionId}: remote=${replayBuffer.length}, cached=${cachedSeq.length} (sequence diverged or shrank)`);
230500
+ cache2.replaceAll(sessionId, [...replayBuffer], replayBuffer.length);
230465
230501
  const clearPatch = {
230466
230502
  JsonPatch: [{
230467
230503
  op: "replace",
@@ -230479,12 +230515,15 @@ function connectPersistentRemoteWs(sessionId, remoteInfo, cache2, reverseConnect
230479
230515
  remoteWs.on("message", handleLiveMessage);
230480
230516
  return;
230481
230517
  }
230482
- if ("JsonPatch" in parsed || "taskCompleted" in parsed || "error" in parsed) {
230518
+ if (isEntryPatchFrame(parsed)) {
230483
230519
  replayBuffer.push(raw);
230520
+ return;
230484
230521
  }
230485
230522
  if ("finished" in parsed) {
230486
230523
  cache2.setFinished(sessionId);
230524
+ return;
230487
230525
  }
230526
+ handleLiveMessage(data);
230488
230527
  });
230489
230528
  }
230490
230529
  remoteWs.on("error", (error48) => {
@@ -238530,16 +238569,12 @@ function resolveUserId(authResult) {
238530
238569
  }
238531
238570
 
238532
238571
  // src/routes/project-routes.ts
238533
- function sanitizeProject(project) {
238534
- const { remote_api_key, ...safe } = project;
238535
- return { ...safe, has_remote_api_key: !!remote_api_key };
238536
- }
238537
238572
  var routes2 = async (fastify2) => {
238538
238573
  fastify2.get("/api/projects", async (req, reply) => {
238539
238574
  const authResult = requireAuth(req, reply);
238540
238575
  if (authResult === null) return;
238541
238576
  const userId = resolveUserId(authResult);
238542
- const projects = (await fastify2.storage.projects.getAll(userId)).map(sanitizeProject);
238577
+ const projects = await fastify2.storage.projects.getAll(userId);
238543
238578
  return reply.code(200).send({ projects });
238544
238579
  });
238545
238580
  fastify2.get("/api/projects/:id", async (req, reply) => {
@@ -238550,7 +238585,7 @@ var routes2 = async (fastify2) => {
238550
238585
  if (!project) {
238551
238586
  return reply.code(404).send({ error: "Project not found" });
238552
238587
  }
238553
- return reply.code(200).send({ project: sanitizeProject(project) });
238588
+ return reply.code(200).send({ project });
238554
238589
  });
238555
238590
  fastify2.post("/api/dialog/select-folder", async (req, reply) => {
238556
238591
  const folderPath = await selectFolder();
@@ -238621,7 +238656,7 @@ var routes2 = async (fastify2) => {
238621
238656
  agent_mode: agentMode,
238622
238657
  executor_mode: executorMode
238623
238658
  }, userId);
238624
- return reply.code(201).send({ project: sanitizeProject(project) });
238659
+ return reply.code(201).send({ project });
238625
238660
  });
238626
238661
  fastify2.put("/api/projects/:id", async (req, reply) => {
238627
238662
  const authResult = requireAuth(req, reply);
@@ -238661,7 +238696,7 @@ var routes2 = async (fastify2) => {
238661
238696
  if (!updated) {
238662
238697
  return reply.code(404).send({ error: "Project not found" });
238663
238698
  }
238664
- return reply.code(200).send({ project: sanitizeProject(updated) });
238699
+ return reply.code(200).send({ project: updated });
238665
238700
  });
238666
238701
  fastify2.delete("/api/projects/:id", async (req, reply) => {
238667
238702
  const authResult = requireAuth(req, reply);
@@ -238800,11 +238835,101 @@ function requireUserFacingOrTrustedProxyUserId(request, reply) {
238800
238835
  return authResult === void 0 && request.server.authEnabled ? void 0 : resolveUserId(authResult);
238801
238836
  }
238802
238837
 
238838
+ // src/update-check.ts
238839
+ var SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/;
238840
+ async function fetchLatestPublishedVersion(pkg = "vibedeckx", fetchImpl = fetch) {
238841
+ try {
238842
+ const response = await fetchImpl(
238843
+ `https://registry.npmjs.org/${pkg}/latest`,
238844
+ { signal: AbortSignal.timeout(4e3) }
238845
+ );
238846
+ if (!response.ok) return void 0;
238847
+ const body = await response.json();
238848
+ return typeof body.version === "string" && parseSemver(body.version) ? body.version : void 0;
238849
+ } catch {
238850
+ return void 0;
238851
+ }
238852
+ }
238853
+ function parseSemver(version2) {
238854
+ const match2 = SEMVER_RE.exec(version2);
238855
+ if (!match2) return void 0;
238856
+ return {
238857
+ core: [match2[1], match2[2], match2[3]],
238858
+ prerelease: match2[4]?.split(".")
238859
+ };
238860
+ }
238861
+ function compareNumericStrings(a, b2) {
238862
+ if (a.length !== b2.length) return a.length - b2.length;
238863
+ return a < b2 ? -1 : a > b2 ? 1 : 0;
238864
+ }
238865
+ function comparePrereleaseIdentifiers(a, b2) {
238866
+ const aNumeric = /^\d+$/.test(a);
238867
+ const bNumeric = /^\d+$/.test(b2);
238868
+ if (aNumeric && bNumeric) return compareNumericStrings(a, b2);
238869
+ if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
238870
+ return a < b2 ? -1 : a > b2 ? 1 : 0;
238871
+ }
238872
+ function compareSemver(a, b2) {
238873
+ for (let i = 0; i < 3; i += 1) {
238874
+ const compared = compareNumericStrings(a.core[i], b2.core[i]);
238875
+ if (compared !== 0) return compared;
238876
+ }
238877
+ if (!a.prerelease && !b2.prerelease) return 0;
238878
+ if (!a.prerelease) return 1;
238879
+ if (!b2.prerelease) return -1;
238880
+ const length = Math.max(a.prerelease.length, b2.prerelease.length);
238881
+ for (let i = 0; i < length; i += 1) {
238882
+ const aId = a.prerelease[i];
238883
+ const bId = b2.prerelease[i];
238884
+ if (aId === void 0) return -1;
238885
+ if (bId === void 0) return 1;
238886
+ const compared = comparePrereleaseIdentifiers(aId, bId);
238887
+ if (compared !== 0) return compared;
238888
+ }
238889
+ return 0;
238890
+ }
238891
+ function compareVersionStrings(a, b2) {
238892
+ const parsedA = parseSemver(a);
238893
+ const parsedB = parseSemver(b2);
238894
+ if (!parsedA || !parsedB) return void 0;
238895
+ return compareSemver(parsedA, parsedB);
238896
+ }
238897
+ function compareUpdateStatus(current, latest) {
238898
+ if (latest === void 0) return "unknown";
238899
+ const currentParsed = parseSemver(current);
238900
+ const latestParsed = parseSemver(latest);
238901
+ if (!currentParsed || !latestParsed) return "unknown";
238902
+ return compareSemver(latestParsed, currentParsed) > 0 ? "update-available" : "up-to-date";
238903
+ }
238904
+
238803
238905
  // src/routes/remote-server-routes.ts
238804
238906
  function sanitizeServer(server) {
238805
238907
  const { connect_token: _t, ...safe } = server;
238806
238908
  return safe;
238807
238909
  }
238910
+ var npmLatestCache;
238911
+ var npmLatestRefreshInFlight = false;
238912
+ function getLatestWorkerVersion() {
238913
+ const ttlMs = npmLatestCache?.value !== void 0 ? 60 * 60 * 1e3 : 5 * 60 * 1e3;
238914
+ const fresh = npmLatestCache !== void 0 && Date.now() - npmLatestCache.fetchedAt < ttlMs;
238915
+ if (!fresh && !npmLatestRefreshInFlight) {
238916
+ npmLatestRefreshInFlight = true;
238917
+ void fetchLatestPublishedVersion().then((value) => {
238918
+ npmLatestCache = { value, fetchedAt: Date.now() };
238919
+ }).finally(() => {
238920
+ npmLatestRefreshInFlight = false;
238921
+ });
238922
+ }
238923
+ return npmLatestCache?.value;
238924
+ }
238925
+ function workerUpdateStatus(workerVersion, latest) {
238926
+ if (!workerVersion) return "unreported";
238927
+ const vsMin = compareVersionStrings(workerVersion, MIN_WORKER_VERSION);
238928
+ if (vsMin !== void 0 && vsMin < 0) return "behind-min";
238929
+ const vsLatest = latest === void 0 ? void 0 : compareVersionStrings(workerVersion, latest);
238930
+ if (vsLatest !== void 0 && vsLatest < 0) return "behind-latest";
238931
+ return "current";
238932
+ }
238808
238933
  var CROSS_REMOTE_ACCESS_VALUES = ["off", "read", "exec"];
238809
238934
  var isCrossRemoteAccess = (value) => typeof value === "string" && CROSS_REMOTE_ACCESS_VALUES.includes(value);
238810
238935
  var routes3 = async (fastify2) => {
@@ -238812,7 +238937,12 @@ var routes3 = async (fastify2) => {
238812
238937
  const userId = requireUserFacingUserId(request, reply);
238813
238938
  if (userId === null) return;
238814
238939
  const servers = await fastify2.storage.remoteServers.getAll(userId);
238815
- return reply.send(servers.map(sanitizeServer));
238940
+ const latest = getLatestWorkerVersion();
238941
+ return reply.send(servers.map((server) => ({
238942
+ ...sanitizeServer(server),
238943
+ latest_worker_version: latest,
238944
+ worker_update_status: workerUpdateStatus(server.worker_version, latest)
238945
+ })));
238816
238946
  });
238817
238947
  fastify2.post("/api/remote-servers", async (request, reply) => {
238818
238948
  const userId = requireUserFacingUserId(request, reply);
@@ -238971,12 +239101,94 @@ var routes3 = async (fastify2) => {
238971
239101
  };
238972
239102
  var remote_server_routes_default = (0, import_fastify_plugin4.default)(routes3, { name: "remote-server-routes" });
238973
239103
 
238974
- // src/routes/project-remote-routes.ts
239104
+ // src/routes/worker-stats-routes.ts
238975
239105
  var import_fastify_plugin5 = __toESM(require_plugin2(), 1);
239106
+ var API_KEY = process.env.VIBEDECKX_API_KEY || void 0;
239107
+ var STALE_WORKER_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1e3;
239108
+ var PHASE4_UNKNOWN_SHARE_MAX = 0.05;
239109
+ var PHASE4_DEPRECATION_DAYS = 60;
239110
+ function parseDbTimestamp3(ts) {
239111
+ if (!ts) return void 0;
239112
+ const ms = Date.parse(ts.replace(" ", "T") + "Z");
239113
+ return Number.isNaN(ms) ? void 0 : ms;
239114
+ }
239115
+ function countByVersion(versions) {
239116
+ const counts = {};
239117
+ for (const version2 of versions) {
239118
+ const key = version2 ?? "unknown";
239119
+ counts[key] = (counts[key] ?? 0) + 1;
239120
+ }
239121
+ return counts;
239122
+ }
239123
+ function oldestVersion(versions) {
239124
+ let oldest;
239125
+ for (const version2 of versions) {
239126
+ if (oldest === void 0) {
239127
+ oldest = version2;
239128
+ continue;
239129
+ }
239130
+ const cmp = compareVersionStrings(version2, oldest);
239131
+ if (cmp !== void 0 && cmp < 0) oldest = version2;
239132
+ }
239133
+ return oldest;
239134
+ }
239135
+ var routes4 = async (fastify2) => {
239136
+ fastify2.get("/api/admin/worker-version-stats", async (request, reply) => {
239137
+ const apiKeyHeader = request.headers["x-vibedeckx-api-key"];
239138
+ const isOperator = API_KEY !== void 0 && apiKeyHeader !== void 0 || !fastify2.authEnabled && API_KEY === void 0;
239139
+ if (!isOperator) return reply.code(404).send({ error: "Not found" });
239140
+ const now3 = Date.now();
239141
+ const servers = await fastify2.storage.remoteServers.getAll();
239142
+ const connected = servers.filter((server) => fastify2.reverseConnectManager.isConnected(server.id));
239143
+ const everActive = servers.filter((server) => server.last_connected_at !== void 0);
239144
+ const connectedVersions = connected.map((server) => server.worker_version);
239145
+ const unknownConnected = connectedVersions.filter((v2) => v2 === void 0).length;
239146
+ const unknownShareConnected = connected.length > 0 ? unknownConnected / connected.length : null;
239147
+ const daysSinceReporting = Math.floor(
239148
+ (now3 - Date.parse(WORKER_VERSION_REPORTING_SINCE)) / (24 * 60 * 60 * 1e3)
239149
+ );
239150
+ return reply.send({
239151
+ min_worker_version: MIN_WORKER_VERSION,
239152
+ reporting_since: WORKER_VERSION_REPORTING_SINCE,
239153
+ workers_total: servers.length,
239154
+ connected_workers: connected.length,
239155
+ // Two lenses on the distribution: _all accumulates dead rows forever
239156
+ // (abandoned trials), _connected is the one Phase 4 decisions use.
239157
+ versions_all: countByVersion(servers.map((server) => server.worker_version)),
239158
+ versions_connected: countByVersion(connectedVersions),
239159
+ oldest_connected_version: oldestVersion(
239160
+ connectedVersions.filter((v2) => v2 !== void 0)
239161
+ ) ?? null,
239162
+ // Deploy blast radius: how many live tunnels a restart would sever, and
239163
+ // how many of those are mid-turn (the actual user harm — idle sessions
239164
+ // recover on reconnect).
239165
+ active_remote_sessions: fastify2.remoteSessionMap.size,
239166
+ active_turns: await fastify2.storage.searchCache.countRunningRemoteSessions(),
239167
+ // Upgrade-failure tripwire: workers that were active once but have been
239168
+ // offline > 7 days — the classic "daemon didn't come back" signature.
239169
+ stale_workers_7d: everActive.filter((server) => {
239170
+ if (fastify2.reverseConnectManager.isConnected(server.id)) return false;
239171
+ const lastSeen = parseDbTimestamp3(server.last_connected_at);
239172
+ return lastSeen !== void 0 && now3 - lastSeen > STALE_WORKER_THRESHOLD_MS;
239173
+ }).length,
239174
+ phase4_ready: {
239175
+ unknown_share_connected: unknownShareConnected,
239176
+ unknown_share_max: PHASE4_UNKNOWN_SHARE_MAX,
239177
+ days_since_reporting_release: daysSinceReporting,
239178
+ deprecation_days: PHASE4_DEPRECATION_DAYS,
239179
+ verdict: unknownShareConnected !== null && unknownShareConnected < PHASE4_UNKNOWN_SHARE_MAX && daysSinceReporting >= PHASE4_DEPRECATION_DAYS
239180
+ }
239181
+ });
239182
+ });
239183
+ };
239184
+ var worker_stats_routes_default = (0, import_fastify_plugin5.default)(routes4, { name: "worker-stats-routes" });
239185
+
239186
+ // src/routes/project-remote-routes.ts
239187
+ var import_fastify_plugin6 = __toESM(require_plugin2(), 1);
238976
239188
  function sanitizeProjectRemote(pr) {
238977
239189
  return pr;
238978
239190
  }
238979
- var routes4 = async (fastify2) => {
239191
+ var routes5 = async (fastify2) => {
238980
239192
  fastify2.get(
238981
239193
  "/api/projects/:id/remotes",
238982
239194
  async (request, reply) => {
@@ -239076,12 +239288,12 @@ var routes4 = async (fastify2) => {
239076
239288
  }
239077
239289
  );
239078
239290
  };
239079
- var project_remote_routes_default = (0, import_fastify_plugin5.default)(routes4, { name: "project-remote-routes" });
239291
+ var project_remote_routes_default = (0, import_fastify_plugin6.default)(routes5, { name: "project-remote-routes" });
239080
239292
 
239081
239293
  // src/routes/executor-group-routes.ts
239082
- var import_fastify_plugin6 = __toESM(require_plugin2(), 1);
239294
+ var import_fastify_plugin7 = __toESM(require_plugin2(), 1);
239083
239295
  import { randomUUID as randomUUID11 } from "crypto";
239084
- var routes5 = async (fastify2) => {
239296
+ var routes6 = async (fastify2) => {
239085
239297
  fastify2.get(
239086
239298
  "/api/projects/:projectId/executor-groups",
239087
239299
  async (req, reply) => {
@@ -239152,17 +239364,17 @@ var routes5 = async (fastify2) => {
239152
239364
  }
239153
239365
  );
239154
239366
  };
239155
- var executor_group_routes_default = (0, import_fastify_plugin6.default)(routes5, { name: "executor-group-routes" });
239367
+ var executor_group_routes_default = (0, import_fastify_plugin7.default)(routes6, { name: "executor-group-routes" });
239156
239368
 
239157
239369
  // src/routes/executor-routes.ts
239158
- var import_fastify_plugin7 = __toESM(require_plugin2(), 1);
239370
+ var import_fastify_plugin8 = __toESM(require_plugin2(), 1);
239159
239371
  import { randomUUID as randomUUID12 } from "crypto";
239160
239372
  function normalizeSqlTimestamp(value) {
239161
239373
  if (!value) return null;
239162
239374
  if (value.includes("T") && /(Z|[+-]\d{2}:?\d{2})$/.test(value)) return value;
239163
239375
  return value.replace(" ", "T") + "Z";
239164
239376
  }
239165
- var routes6 = async (fastify2) => {
239377
+ var routes7 = async (fastify2) => {
239166
239378
  fastify2.get(
239167
239379
  "/api/projects/:projectId/executors",
239168
239380
  async (req, reply) => {
@@ -239301,13 +239513,13 @@ var routes6 = async (fastify2) => {
239301
239513
  return reply.code(200).send({ success: true });
239302
239514
  });
239303
239515
  };
239304
- var executor_routes_default = (0, import_fastify_plugin7.default)(routes6, { name: "executor-routes" });
239516
+ var executor_routes_default = (0, import_fastify_plugin8.default)(routes7, { name: "executor-routes" });
239305
239517
 
239306
239518
  // src/routes/process-routes.ts
239307
- var import_fastify_plugin8 = __toESM(require_plugin2(), 1);
239519
+ var import_fastify_plugin9 = __toESM(require_plugin2(), 1);
239308
239520
  import path9 from "path";
239309
239521
  import { randomUUID as randomUUID13 } from "crypto";
239310
- var routes7 = async (fastify2) => {
239522
+ var routes8 = async (fastify2) => {
239311
239523
  fastify2.post("/api/path/execute", async (req, reply) => {
239312
239524
  const userId = requireAuth(req, reply);
239313
239525
  if (userId === null) return;
@@ -239519,10 +239731,10 @@ var routes7 = async (fastify2) => {
239519
239731
  return reply.code(200).send({ processes });
239520
239732
  });
239521
239733
  };
239522
- var process_routes_default = (0, import_fastify_plugin8.default)(routes7, { name: "process-routes" });
239734
+ var process_routes_default = (0, import_fastify_plugin9.default)(routes8, { name: "process-routes" });
239523
239735
 
239524
239736
  // src/routes/worktree-routes.ts
239525
- var import_fastify_plugin9 = __toESM(require_plugin2(), 1);
239737
+ var import_fastify_plugin10 = __toESM(require_plugin2(), 1);
239526
239738
  import { mkdir as mkdir3 } from "fs/promises";
239527
239739
  async function getAllRemoteConfigs(fastify2, project) {
239528
239740
  const remotes = await fastify2.storage.projectRemotes.getByProject(project.id);
@@ -239535,7 +239747,7 @@ async function getRemoteConfig(fastify2, project) {
239535
239747
  const all = await getAllRemoteConfigs(fastify2, project);
239536
239748
  return all.length > 0 ? all[0] : null;
239537
239749
  }
239538
- var routes8 = async (fastify2) => {
239750
+ var routes9 = async (fastify2) => {
239539
239751
  fastify2.get("/api/path/branches", async (req, reply) => {
239540
239752
  const projectPath = req.query.path;
239541
239753
  if (!projectPath) {
@@ -240063,10 +240275,10 @@ var routes8 = async (fastify2) => {
240063
240275
  });
240064
240276
  });
240065
240277
  };
240066
- var worktree_routes_default = (0, import_fastify_plugin9.default)(routes8, { name: "worktree-routes" });
240278
+ var worktree_routes_default = (0, import_fastify_plugin10.default)(routes9, { name: "worktree-routes" });
240067
240279
 
240068
240280
  // src/routes/diff-routes.ts
240069
- var import_fastify_plugin10 = __toESM(require_plugin2(), 1);
240281
+ var import_fastify_plugin11 = __toESM(require_plugin2(), 1);
240070
240282
  import path10 from "path";
240071
240283
  import { readFileSync as readFileSync4 } from "fs";
240072
240284
  import { execFileSync as execFileSync5 } from "child_process";
@@ -240374,7 +240586,7 @@ async function getUntrackedFiles(cwd) {
240374
240586
  }
240375
240587
  return files;
240376
240588
  }
240377
- var routes9 = async (fastify2) => {
240589
+ var routes10 = async (fastify2) => {
240378
240590
  fastify2.get("/api/path/diff", async (req, reply) => {
240379
240591
  const projectPath = req.query.path;
240380
240592
  if (!projectPath) {
@@ -240565,10 +240777,10 @@ var routes9 = async (fastify2) => {
240565
240777
  }
240566
240778
  });
240567
240779
  };
240568
- var diff_routes_default = (0, import_fastify_plugin10.default)(routes9, { name: "diff-routes" });
240780
+ var diff_routes_default = (0, import_fastify_plugin11.default)(routes10, { name: "diff-routes" });
240569
240781
 
240570
240782
  // src/routes/file-routes.ts
240571
- var import_fastify_plugin11 = __toESM(require_plugin2(), 1);
240783
+ var import_fastify_plugin12 = __toESM(require_plugin2(), 1);
240572
240784
  import path11 from "path";
240573
240785
  import fs2 from "fs/promises";
240574
240786
  import { createReadStream, constants as fsConstants } from "fs";
@@ -240791,7 +241003,7 @@ async function searchSymbol(basePath33, symbol26) {
240791
241003
  }
240792
241004
  return { hits, truncated };
240793
241005
  }
240794
- var routes10 = async (fastify2) => {
241006
+ var routes11 = async (fastify2) => {
240795
241007
  fastify2.get("/api/path/browse", async (req, reply) => {
240796
241008
  const projectPath = req.query.path;
240797
241009
  if (!projectPath) {
@@ -241352,10 +241564,10 @@ var routes10 = async (fastify2) => {
241352
241564
  }
241353
241565
  });
241354
241566
  };
241355
- var file_routes_default = (0, import_fastify_plugin11.default)(routes10, { name: "file-routes" });
241567
+ var file_routes_default = (0, import_fastify_plugin12.default)(routes11, { name: "file-routes" });
241356
241568
 
241357
241569
  // src/routes/agent-session-routes.ts
241358
- var import_fastify_plugin12 = __toESM(require_plugin2(), 1);
241570
+ var import_fastify_plugin13 = __toESM(require_plugin2(), 1);
241359
241571
 
241360
241572
  // src/utils/paste-file.ts
241361
241573
  import { chmod, mkdir as mkdir4, open } from "node:fs/promises";
@@ -241406,7 +241618,7 @@ function messageTextLength(content) {
241406
241618
  }
241407
241619
  return n;
241408
241620
  }
241409
- var routes11 = async (fastify2) => {
241621
+ var routes12 = async (fastify2) => {
241410
241622
  const instructionReceiverToken = randomUUID15();
241411
241623
  const instructionDeliveryLocks = /* @__PURE__ */ new Map();
241412
241624
  async function serializeInstructionDelivery(key, effect) {
@@ -242760,10 +242972,10 @@ var routes11 = async (fastify2) => {
242760
242972
  return reply.code(200).send({ success: true, favorited });
242761
242973
  });
242762
242974
  };
242763
- var agent_session_routes_default = (0, import_fastify_plugin12.default)(routes11, { name: "agent-session-routes" });
242975
+ var agent_session_routes_default = (0, import_fastify_plugin13.default)(routes12, { name: "agent-session-routes" });
242764
242976
 
242765
242977
  // src/routes/branch-activity-routes.ts
242766
- var import_fastify_plugin13 = __toESM(require_plugin2(), 1);
242978
+ var import_fastify_plugin14 = __toESM(require_plugin2(), 1);
242767
242979
  function toResponse(map2) {
242768
242980
  return {
242769
242981
  branches: Array.from(map2.entries()).map(([branch, state]) => ({
@@ -242778,7 +242990,7 @@ function fromResponse(resp) {
242778
242990
  resp.branches.map((b2) => [b2.branch ?? "", { activity: b2.activity, since: b2.since }])
242779
242991
  );
242780
242992
  }
242781
- var routes12 = async (fastify2) => {
242993
+ var routes13 = async (fastify2) => {
242782
242994
  function proxyAuto(remoteServerId, method, apiPath, body) {
242783
242995
  return proxyToRemoteAuto(remoteServerId, method, apiPath, body, {
242784
242996
  reverseConnectManager: fastify2.reverseConnectManager
@@ -242843,10 +243055,10 @@ var routes12 = async (fastify2) => {
242843
243055
  }
242844
243056
  );
242845
243057
  };
242846
- var branch_activity_routes_default = (0, import_fastify_plugin13.default)(routes12, { name: "branch-activity-routes" });
243058
+ var branch_activity_routes_default = (0, import_fastify_plugin14.default)(routes13, { name: "branch-activity-routes" });
242847
243059
 
242848
243060
  // src/routes/merge-status-routes.ts
242849
- var import_fastify_plugin14 = __toESM(require_plugin2(), 1);
243061
+ var import_fastify_plugin15 = __toESM(require_plugin2(), 1);
242850
243062
  function resolveTargets(comparisons, storedTargets) {
242851
243063
  const sources = [];
242852
243064
  const effective = comparisons.map((comparison) => {
@@ -242960,7 +243172,7 @@ function sendComputed(reply, repoPath, comparisons, repository) {
242960
243172
  return reply.code(statusCode).send({ error: message });
242961
243173
  }
242962
243174
  }
242963
- var routes13 = async (fastify2) => {
243175
+ var routes14 = async (fastify2) => {
242964
243176
  fastify2.post(
242965
243177
  "/api/path/branches/merge-status",
242966
243178
  async (req, reply) => {
@@ -243082,11 +243294,11 @@ var routes13 = async (fastify2) => {
243082
243294
  }
243083
243295
  );
243084
243296
  };
243085
- var merge_status_routes_default = (0, import_fastify_plugin14.default)(routes13, { name: "merge-status-routes" });
243297
+ var merge_status_routes_default = (0, import_fastify_plugin15.default)(routes14, { name: "merge-status-routes" });
243086
243298
 
243087
243299
  // src/routes/chat-session-routes.ts
243088
- var import_fastify_plugin15 = __toESM(require_plugin2(), 1);
243089
- var routes14 = async (fastify2) => {
243300
+ var import_fastify_plugin16 = __toESM(require_plugin2(), 1);
243301
+ var routes15 = async (fastify2) => {
243090
243302
  const getAuthorizedSession = (req, reply, sessionId) => {
243091
243303
  const authResult = requireUserFacingUserId(req, reply);
243092
243304
  if (authResult === null) return null;
@@ -243191,10 +243403,10 @@ var routes14 = async (fastify2) => {
243191
243403
  return reply.send({ ok: true });
243192
243404
  });
243193
243405
  };
243194
- var chat_session_routes_default = (0, import_fastify_plugin15.default)(routes14, { name: "chat-session-routes" });
243406
+ var chat_session_routes_default = (0, import_fastify_plugin16.default)(routes15, { name: "chat-session-routes" });
243195
243407
 
243196
243408
  // src/routes/project-chat-routes.ts
243197
- var import_fastify_plugin16 = __toESM(require_plugin2(), 1);
243409
+ var import_fastify_plugin17 = __toESM(require_plugin2(), 1);
243198
243410
  import { createHash as createHash6, randomUUID as randomUUID16 } from "crypto";
243199
243411
  var MAX_TITLE_LENGTH = 200;
243200
243412
  var MAX_MESSAGE_LENGTH = 1e5;
@@ -243307,7 +243519,7 @@ function parseWorkspaceSelectionBody(body) {
243307
243519
  if (!requestId || requestId.length > MAX_TOOL_SELECTOR_ID || !workspaceId || workspaceId.length > MAX_TOOL_SELECTOR_ID) return null;
243308
243520
  return { requestId, workspaceId };
243309
243521
  }
243310
- var routes15 = async (fastify2) => {
243522
+ var routes16 = async (fastify2) => {
243311
243523
  const getOwnedThread = async (req, reply, threadId) => {
243312
243524
  const authResult = requireAuth(req, reply);
243313
243525
  if (authResult === null) return null;
@@ -243526,10 +243738,10 @@ var routes15 = async (fastify2) => {
243526
243738
  }
243527
243739
  });
243528
243740
  };
243529
- var project_chat_routes_default = (0, import_fastify_plugin16.default)(routes15, { name: "project-chat-routes" });
243741
+ var project_chat_routes_default = (0, import_fastify_plugin17.default)(routes16, { name: "project-chat-routes" });
243530
243742
 
243531
243743
  // src/routes/project-activity-routes.ts
243532
- var import_fastify_plugin17 = __toESM(require_plugin2(), 1);
243744
+ var import_fastify_plugin18 = __toESM(require_plugin2(), 1);
243533
243745
 
243534
243746
  // src/project-activity.ts
243535
243747
  var RECENT_THREAD_LIMIT = 5;
@@ -243537,7 +243749,7 @@ var RECENT_SESSION_LIMIT = 8;
243537
243749
  var RECENT_RUN_LIMIT = 5;
243538
243750
  var PRIORITY_TASK_LIMIT = 5;
243539
243751
  var ATTENTION_LIMIT = 10;
243540
- var parseDbTimestamp3 = (value) => {
243752
+ var parseDbTimestamp4 = (value) => {
243541
243753
  if (!value) return null;
243542
243754
  const explicitZone = /(?:Z|[+-]\d\d:\d\d)$/i.test(value);
243543
243755
  const parsed = Date.parse(explicitZone ? value : `${value.replace(" ", "T")}Z`);
@@ -243562,8 +243774,8 @@ var localActivity = (session) => {
243562
243774
  lastActiveAt: maxTimestamp(
243563
243775
  session.last_user_message_at,
243564
243776
  session.last_completed_at,
243565
- parseDbTimestamp3(session.updated_at),
243566
- parseDbTimestamp3(session.created_at)
243777
+ parseDbTimestamp4(session.updated_at),
243778
+ parseDbTimestamp4(session.created_at)
243567
243779
  ),
243568
243780
  lastUserMessageAt: session.last_user_message_at ?? null,
243569
243781
  lastCompletedAt: session.last_completed_at ?? null
@@ -243627,7 +243839,7 @@ async function getProjectActivity(storage, projectId, userId) {
243627
243839
  entityId: run2.id,
243628
243840
  status: run2.status,
243629
243841
  title: run2.scheduleName,
243630
- occurredAtMs: parseDbTimestamp3(run2.finished_at ?? run2.started_at) ?? 0
243842
+ occurredAtMs: parseDbTimestamp4(run2.finished_at ?? run2.started_at) ?? 0
243631
243843
  }))
243632
243844
  ].sort((left, right) => right.occurredAtMs - left.occurredAtMs || right.entityId.localeCompare(left.entityId)).slice(0, ATTENTION_LIMIT).map(({ occurredAtMs, ...item }) => ({
243633
243845
  ...item,
@@ -243647,7 +243859,7 @@ async function getProjectActivity(storage, projectId, userId) {
243647
243859
  }
243648
243860
 
243649
243861
  // src/routes/project-activity-routes.ts
243650
- var routes16 = async (fastify2) => {
243862
+ var routes17 = async (fastify2) => {
243651
243863
  fastify2.get(
243652
243864
  "/api/projects/:projectId/activity",
243653
243865
  async (req, reply) => {
@@ -243663,12 +243875,12 @@ var routes16 = async (fastify2) => {
243663
243875
  }
243664
243876
  );
243665
243877
  };
243666
- var project_activity_routes_default = (0, import_fastify_plugin17.default)(routes16);
243878
+ var project_activity_routes_default = (0, import_fastify_plugin18.default)(routes17);
243667
243879
 
243668
243880
  // src/routes/task-routes.ts
243669
- var import_fastify_plugin18 = __toESM(require_plugin2(), 1);
243881
+ var import_fastify_plugin19 = __toESM(require_plugin2(), 1);
243670
243882
  import { randomUUID as randomUUID17 } from "crypto";
243671
- var routes17 = async (fastify2) => {
243883
+ var routes18 = async (fastify2) => {
243672
243884
  fastify2.get(
243673
243885
  "/api/projects/:projectId/tasks",
243674
243886
  async (req, reply) => {
@@ -243828,12 +244040,12 @@ Description: ${description}`,
243828
244040
  return reply.code(200).send({ success: true });
243829
244041
  });
243830
244042
  };
243831
- var task_routes_default = (0, import_fastify_plugin18.default)(routes17, { name: "task-routes" });
244043
+ var task_routes_default = (0, import_fastify_plugin19.default)(routes18, { name: "task-routes" });
243832
244044
 
243833
244045
  // src/routes/rule-routes.ts
243834
- var import_fastify_plugin19 = __toESM(require_plugin2(), 1);
244046
+ var import_fastify_plugin20 = __toESM(require_plugin2(), 1);
243835
244047
  import { randomUUID as randomUUID18 } from "crypto";
243836
- var routes18 = async (fastify2) => {
244048
+ var routes19 = async (fastify2) => {
243837
244049
  fastify2.get(
243838
244050
  "/api/projects/:projectId/rules",
243839
244051
  async (req, reply) => {
@@ -243919,12 +244131,12 @@ var routes18 = async (fastify2) => {
243919
244131
  return reply.code(200).send({ success: true });
243920
244132
  });
243921
244133
  };
243922
- var rule_routes_default = (0, import_fastify_plugin19.default)(routes18, { name: "rule-routes" });
244134
+ var rule_routes_default = (0, import_fastify_plugin20.default)(routes19, { name: "rule-routes" });
243923
244135
 
243924
244136
  // src/routes/command-routes.ts
243925
- var import_fastify_plugin20 = __toESM(require_plugin2(), 1);
244137
+ var import_fastify_plugin21 = __toESM(require_plugin2(), 1);
243926
244138
  import { randomUUID as randomUUID19 } from "crypto";
243927
- var routes19 = async (fastify2) => {
244139
+ var routes20 = async (fastify2) => {
243928
244140
  fastify2.get(
243929
244141
  "/api/projects/:projectId/commands",
243930
244142
  async (req, reply) => {
@@ -243993,10 +244205,10 @@ var routes19 = async (fastify2) => {
243993
244205
  return reply.code(200).send({ success: true });
243994
244206
  });
243995
244207
  };
243996
- var command_routes_default = (0, import_fastify_plugin20.default)(routes19, { name: "command-routes" });
244208
+ var command_routes_default = (0, import_fastify_plugin21.default)(routes20, { name: "command-routes" });
243997
244209
 
243998
244210
  // src/routes/workflow-run-routes.ts
243999
- var import_fastify_plugin21 = __toESM(require_plugin2(), 1);
244211
+ var import_fastify_plugin22 = __toESM(require_plugin2(), 1);
244000
244212
 
244001
244213
  // src/utils/review-brief.ts
244002
244214
  var BASE_TIMEOUT_MS = 15e3;
@@ -244328,7 +244540,7 @@ function errStatus(err) {
244328
244540
  return 500;
244329
244541
  }
244330
244542
  }
244331
- async function routes20(fastify2) {
244543
+ async function routes21(fastify2) {
244332
244544
  const remoteRunMap = /* @__PURE__ */ new Map();
244333
244545
  const TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "failed"]);
244334
244546
  const trackRemoteRun = (localRun, info) => {
@@ -244793,10 +245005,10 @@ async function routes20(fastify2) {
244793
245005
  return reply.send({ runs });
244794
245006
  });
244795
245007
  }
244796
- var workflow_run_routes_default = (0, import_fastify_plugin21.default)(routes20, { name: "workflow-run-routes" });
245008
+ var workflow_run_routes_default = (0, import_fastify_plugin22.default)(routes21, { name: "workflow-run-routes" });
244797
245009
 
244798
245010
  // src/routes/settings-routes.ts
244799
- var import_fastify_plugin22 = __toESM(require_plugin2(), 1);
245011
+ var import_fastify_plugin23 = __toESM(require_plugin2(), 1);
244800
245012
  var SettingsValidationError = class extends Error {
244801
245013
  };
244802
245014
  var DEFAULT_PROXY_CONFIG = { type: "none", host: "", port: 0 };
@@ -244860,7 +245072,7 @@ function requireSettingsUser(req, reply) {
244860
245072
  if (auth === null) return null;
244861
245073
  return resolveUserId(auth);
244862
245074
  }
244863
- var routes21 = async (fastify2) => {
245075
+ var routes22 = async (fastify2) => {
244864
245076
  fastify2.get("/api/settings/agent-processes", async (req, reply) => {
244865
245077
  if (requireAuth(req, reply) === null) return;
244866
245078
  const saved = await fastify2.storage.settings.get("agentProcesses");
@@ -245134,11 +245346,11 @@ var routes21 = async (fastify2) => {
245134
245346
  return reply.code(200).send(updated);
245135
245347
  });
245136
245348
  };
245137
- var settings_routes_default = (0, import_fastify_plugin22.default)(routes21, { name: "settings-routes" });
245349
+ var settings_routes_default = (0, import_fastify_plugin23.default)(routes22, { name: "settings-routes" });
245138
245350
 
245139
245351
  // src/routes/translate-routes.ts
245140
- var import_fastify_plugin23 = __toESM(require_plugin2(), 1);
245141
- var routes22 = async (fastify2) => {
245352
+ var import_fastify_plugin24 = __toESM(require_plugin2(), 1);
245353
+ var routes23 = async (fastify2) => {
245142
245354
  fastify2.post(
245143
245355
  "/api/translate",
245144
245356
  async (req, reply) => {
@@ -245179,10 +245391,10 @@ ${text2}`,
245179
245391
  }
245180
245392
  );
245181
245393
  };
245182
- var translate_routes_default = (0, import_fastify_plugin23.default)(routes22, { name: "translate-routes" });
245394
+ var translate_routes_default = (0, import_fastify_plugin24.default)(routes23, { name: "translate-routes" });
245183
245395
 
245184
245396
  // src/routes/websocket-routes.ts
245185
- var import_fastify_plugin24 = __toESM(require_plugin2(), 1);
245397
+ var import_fastify_plugin25 = __toESM(require_plugin2(), 1);
245186
245398
 
245187
245399
  // src/routes/executor-stream-handlers.ts
245188
245400
  import { randomUUID as randomUUID20 } from "crypto";
@@ -245294,6 +245506,7 @@ function attachRemoteProcessStream(fastify2, processId, send, onTerminal) {
245294
245506
  } catch {
245295
245507
  }
245296
245508
  if (!parsed) return;
245509
+ if ("keepalive" in parsed) return;
245297
245510
  send(parsed);
245298
245511
  if (parsed.type === "finished" || parsed.type === "error") terminalSignalSent = true;
245299
245512
  if (parsed.type === "finished" || parsed.type === "error") {
@@ -245393,7 +245606,6 @@ async function verifyWsToken(token) {
245393
245606
  }
245394
245607
  async function authenticateWs(authEnabled, query, socket) {
245395
245608
  if (!authEnabled) return { userId: null, kind: "solo" };
245396
- if (process.env.VIBEDECKX_API_KEY && query.apiKey) return { userId: null, kind: "api_key" };
245397
245609
  const reject = (error48) => {
245398
245610
  try {
245399
245611
  socket.send(JSON.stringify({ error: error48 }));
@@ -245449,8 +245661,49 @@ async function userOwnsSession(fastify2, sessionId, userId) {
245449
245661
  return !!await fastify2.storage.projects.getById(session.project_id, userId);
245450
245662
  }
245451
245663
 
245664
+ // src/utils/ws-heartbeat.ts
245665
+ var DEFAULT_INTERVAL_MS = 3e4;
245666
+ function attachWsHeartbeat(socket, { label, intervalMs = DEFAULT_INTERVAL_MS, keepalive = false }) {
245667
+ let awaitingPong = false;
245668
+ const markAlive = () => {
245669
+ awaitingPong = false;
245670
+ };
245671
+ socket.on("pong", markAlive);
245672
+ socket.on("message", markAlive);
245673
+ const timer = setInterval(() => {
245674
+ if (socket.readyState !== wrapper_default.OPEN) return;
245675
+ if (awaitingPong) {
245676
+ console.log(`[WsHeartbeat] ${label}: no pong within ${intervalMs * 2}ms \u2014 terminating dead socket`);
245677
+ try {
245678
+ socket.terminate();
245679
+ } catch {
245680
+ }
245681
+ return;
245682
+ }
245683
+ awaitingPong = true;
245684
+ try {
245685
+ socket.ping();
245686
+ } catch {
245687
+ }
245688
+ if (keepalive) {
245689
+ try {
245690
+ socket.send(JSON.stringify({ keepalive: Date.now() }));
245691
+ } catch {
245692
+ }
245693
+ }
245694
+ }, intervalMs);
245695
+ let cleaned = false;
245696
+ return () => {
245697
+ if (cleaned) return;
245698
+ cleaned = true;
245699
+ clearInterval(timer);
245700
+ socket.off("pong", markAlive);
245701
+ socket.off("message", markAlive);
245702
+ };
245703
+ }
245704
+
245452
245705
  // src/routes/websocket-routes.ts
245453
- var routes23 = async (fastify2) => {
245706
+ var routes24 = async (fastify2) => {
245454
245707
  fastify2.reverseConnectManager.setStatusChangeHandler((remoteServerId, status) => {
245455
245708
  if (status !== "online") return;
245456
245709
  const cache2 = fastify2.remotePatchCache;
@@ -245507,10 +245760,14 @@ var routes23 = async (fastify2) => {
245507
245760
  }
245508
245761
  return;
245509
245762
  }
245763
+ const stopHeartbeat = attachWsHeartbeat(socket, {
245764
+ label: `ProjectChatWS thread=${req.params.threadId}`
245765
+ });
245510
245766
  let cleaned = false;
245511
245767
  const cleanup = () => {
245512
245768
  if (cleaned) return;
245513
245769
  cleaned = true;
245770
+ stopHeartbeat();
245514
245771
  unsubscribe();
245515
245772
  };
245516
245773
  socket.on("close", cleanup);
@@ -245542,11 +245799,9 @@ var routes23 = async (fastify2) => {
245542
245799
  return;
245543
245800
  }
245544
245801
  console.log(`[WebSocket] Client connected for process ${processId}`);
245545
- const pingInterval = setInterval(() => {
245546
- if (socket.readyState === wrapper_default.OPEN) {
245547
- socket.ping();
245548
- }
245549
- }, 3e4);
245802
+ const stopHeartbeat = attachWsHeartbeat(socket, {
245803
+ label: `ExecutorWS process=${processId}`
245804
+ });
245550
245805
  const send = (msg) => {
245551
245806
  try {
245552
245807
  socket.send(JSON.stringify(msg));
@@ -245572,7 +245827,7 @@ var routes23 = async (fastify2) => {
245572
245827
  });
245573
245828
  socket.on("close", () => {
245574
245829
  console.log(`[WebSocket] Client disconnected from process ${processId}`);
245575
- clearInterval(pingInterval);
245830
+ stopHeartbeat();
245576
245831
  handle.cleanup();
245577
245832
  });
245578
245833
  }
@@ -245588,11 +245843,7 @@ var routes23 = async (fastify2) => {
245588
245843
  return;
245589
245844
  }
245590
245845
  console.log(`[ExecutorMux] Client connected`);
245591
- const pingInterval = setInterval(() => {
245592
- if (socket.readyState === wrapper_default.OPEN) {
245593
- socket.ping();
245594
- }
245595
- }, 3e4);
245846
+ const stopHeartbeat = attachWsHeartbeat(socket, { label: "ExecutorMux" });
245596
245847
  const subs = /* @__PURE__ */ new Map();
245597
245848
  const handleInputMap = /* @__PURE__ */ new Map();
245598
245849
  const subscribeProcess = async (processId) => {
@@ -245651,7 +245902,7 @@ var routes23 = async (fastify2) => {
245651
245902
  });
245652
245903
  socket.on("close", () => {
245653
245904
  console.log(`[ExecutorMux] Client disconnected; cleaning ${subs.size} subscriptions`);
245654
- clearInterval(pingInterval);
245905
+ stopHeartbeat();
245655
245906
  for (const cleanup of subs.values()) cleanup();
245656
245907
  subs.clear();
245657
245908
  handleInputMap.clear();
@@ -245666,25 +245917,21 @@ var routes23 = async (fastify2) => {
245666
245917
  console.log(`[AgentWS] Connection attempt for session ${sessionId} (auth=${fastify2.authEnabled})`);
245667
245918
  let principalUserId = fastify2.authEnabled ? null : "local";
245668
245919
  if (fastify2.authEnabled) {
245669
- const apiKey = req.query.apiKey;
245670
245920
  const token = req.query.token;
245671
- const apiKeyTrusted = !!process.env.VIBEDECKX_API_KEY && !!apiKey;
245672
- if (!apiKeyTrusted) {
245673
- if (!token) {
245674
- console.log(`[AgentWS] Auth rejected: no token (session=${sessionId})`);
245675
- socket.send(JSON.stringify({ error: "Authentication required" }));
245676
- socket.close();
245677
- return;
245678
- }
245679
- const userId = await verifyWsToken(token);
245680
- if (!userId) {
245681
- console.log(`[AgentWS] Auth rejected: invalid token (session=${sessionId})`);
245682
- socket.send(JSON.stringify({ error: "Invalid authentication token" }));
245683
- socket.close();
245684
- return;
245685
- }
245686
- principalUserId = userId;
245921
+ if (!token) {
245922
+ console.log(`[AgentWS] Auth rejected: no token (session=${sessionId})`);
245923
+ socket.send(JSON.stringify({ error: "Authentication required" }));
245924
+ socket.close();
245925
+ return;
245926
+ }
245927
+ const userId = await verifyWsToken(token);
245928
+ if (!userId) {
245929
+ console.log(`[AgentWS] Auth rejected: invalid token (session=${sessionId})`);
245930
+ socket.send(JSON.stringify({ error: "Invalid authentication token" }));
245931
+ socket.close();
245932
+ return;
245687
245933
  }
245934
+ principalUserId = userId;
245688
245935
  }
245689
245936
  if (principalUserId !== null && !await userOwnsSession(fastify2, sessionId, principalUserId)) {
245690
245937
  console.log(`[AgentWS] Ownership denied for session ${sessionId} (user=${principalUserId})`);
@@ -245699,15 +245946,15 @@ var routes23 = async (fastify2) => {
245699
245946
  return;
245700
245947
  }
245701
245948
  console.log(`[AgentWS] Client connected for session ${sessionId}`);
245702
- const pingInterval = setInterval(() => {
245703
- if (socket.readyState === wrapper_default.OPEN) {
245704
- socket.ping();
245705
- }
245706
- }, 3e4);
245949
+ const stopHeartbeat = attachWsHeartbeat(socket, {
245950
+ label: `AgentWS session=${sessionId}`,
245951
+ keepalive: true
245952
+ });
245707
245953
  if (sessionId.startsWith("remote-")) {
245708
245954
  const remoteInfo = fastify2.remoteSessionMap.get(sessionId);
245709
245955
  if (!remoteInfo) {
245710
245956
  console.log(`[AgentWS] Remote session ${sessionId} not found in map`);
245957
+ stopHeartbeat();
245711
245958
  socket.send(JSON.stringify({ type: "error", message: "Remote session not found" }));
245712
245959
  socket.close();
245713
245960
  return;
@@ -245734,7 +245981,7 @@ var routes23 = async (fastify2) => {
245734
245981
  }
245735
245982
  cache2.addSubscriber(sessionId, socket);
245736
245983
  socket.on("close", () => {
245737
- clearInterval(pingInterval);
245984
+ stopHeartbeat();
245738
245985
  cache2.removeSubscriber(sessionId, socket);
245739
245986
  });
245740
245987
  return;
@@ -245775,7 +246022,7 @@ var routes23 = async (fastify2) => {
245775
246022
  });
245776
246023
  socket.on("close", () => {
245777
246024
  console.log(`[AgentWS] Client disconnected from remote session ${sessionId}`);
245778
- clearInterval(pingInterval);
246025
+ stopHeartbeat();
245779
246026
  cache2.removeSubscriber(sessionId, socket);
245780
246027
  });
245781
246028
  return;
@@ -245783,7 +246030,7 @@ var routes23 = async (fastify2) => {
245783
246030
  const unsubscribe = fastify2.agentSessionManager.subscribe(sessionId, socket);
245784
246031
  if (!unsubscribe) {
245785
246032
  console.log(`[AgentWS] Session ${sessionId} not found`);
245786
- clearInterval(pingInterval);
246033
+ stopHeartbeat();
245787
246034
  socket.send(JSON.stringify({ error: "Session not found" }));
245788
246035
  socket.close();
245789
246036
  return;
@@ -245802,7 +246049,7 @@ var routes23 = async (fastify2) => {
245802
246049
  });
245803
246050
  socket.on("close", () => {
245804
246051
  console.log(`[AgentWS] Client disconnected from session ${sessionId}`);
245805
- clearInterval(pingInterval);
246052
+ stopHeartbeat();
245806
246053
  unsubscribe?.();
245807
246054
  });
245808
246055
  }
@@ -245814,37 +246061,33 @@ var routes23 = async (fastify2) => {
245814
246061
  const { sessionId } = req.params;
245815
246062
  let principalUserId = fastify2.authEnabled ? null : "local";
245816
246063
  if (fastify2.authEnabled) {
245817
- const apiKey = req.query.apiKey;
245818
246064
  const token = req.query.token;
245819
- const apiKeyTrusted = !!process.env.VIBEDECKX_API_KEY && !!apiKey;
245820
- if (!apiKeyTrusted) {
245821
- if (!token) {
245822
- console.log(`[ChatWS] Auth rejected: no token (session=${sessionId})`);
245823
- try {
245824
- socket.send(JSON.stringify({ error: "Authentication required" }));
245825
- } catch {
245826
- }
245827
- try {
245828
- socket.close();
245829
- } catch {
245830
- }
245831
- return;
246065
+ if (!token) {
246066
+ console.log(`[ChatWS] Auth rejected: no token (session=${sessionId})`);
246067
+ try {
246068
+ socket.send(JSON.stringify({ error: "Authentication required" }));
246069
+ } catch {
245832
246070
  }
245833
- const userId = await verifyWsToken(token);
245834
- if (!userId) {
245835
- console.log(`[ChatWS] Auth rejected: invalid token (session=${sessionId})`);
245836
- try {
245837
- socket.send(JSON.stringify({ error: "Invalid authentication token" }));
245838
- } catch {
245839
- }
245840
- try {
245841
- socket.close();
245842
- } catch {
245843
- }
245844
- return;
246071
+ try {
246072
+ socket.close();
246073
+ } catch {
246074
+ }
246075
+ return;
246076
+ }
246077
+ const userId = await verifyWsToken(token);
246078
+ if (!userId) {
246079
+ console.log(`[ChatWS] Auth rejected: invalid token (session=${sessionId})`);
246080
+ try {
246081
+ socket.send(JSON.stringify({ error: "Invalid authentication token" }));
246082
+ } catch {
245845
246083
  }
245846
- principalUserId = userId;
246084
+ try {
246085
+ socket.close();
246086
+ } catch {
246087
+ }
246088
+ return;
245847
246089
  }
246090
+ principalUserId = userId;
245848
246091
  }
245849
246092
  if (principalUserId !== null) {
245850
246093
  const owned = fastify2.chatSessionManager.getSession(sessionId);
@@ -245862,15 +246105,11 @@ var routes23 = async (fastify2) => {
245862
246105
  }
245863
246106
  }
245864
246107
  console.log(`[ChatWS] Client connected for session ${sessionId}`);
245865
- const pingInterval = setInterval(() => {
245866
- if (socket.readyState === wrapper_default.OPEN) {
245867
- socket.ping();
245868
- }
245869
- }, 3e4);
246108
+ const stopHeartbeat = attachWsHeartbeat(socket, { label: `ChatWS session=${sessionId}` });
245870
246109
  const unsubscribe = fastify2.chatSessionManager.subscribe(sessionId, socket);
245871
246110
  if (!unsubscribe) {
245872
246111
  console.log(`[ChatWS] Session ${sessionId} not found`);
245873
- clearInterval(pingInterval);
246112
+ stopHeartbeat();
245874
246113
  socket.send(JSON.stringify({ error: "Session not found" }));
245875
246114
  socket.close();
245876
246115
  return;
@@ -245890,17 +246129,17 @@ var routes23 = async (fastify2) => {
245890
246129
  });
245891
246130
  socket.on("close", () => {
245892
246131
  console.log(`[ChatWS] Client disconnected from session ${sessionId}`);
245893
- clearInterval(pingInterval);
246132
+ stopHeartbeat();
245894
246133
  unsubscribe?.();
245895
246134
  });
245896
246135
  }
245897
246136
  );
245898
246137
  });
245899
246138
  };
245900
- var websocket_routes_default = (0, import_fastify_plugin24.default)(routes23, { name: "websocket-routes" });
246139
+ var websocket_routes_default = (0, import_fastify_plugin25.default)(routes24, { name: "websocket-routes" });
245901
246140
 
245902
246141
  // src/routes/reverse-connect-routes.ts
245903
- var import_fastify_plugin25 = __toESM(require_plugin2(), 1);
246142
+ var import_fastify_plugin26 = __toESM(require_plugin2(), 1);
245904
246143
  import { randomBytes as randomBytes2, createHash as createHash7, verify as cryptoVerify } from "crypto";
245905
246144
 
245906
246145
  // src/utils/rate-limited-warn.ts
@@ -245936,73 +246175,6 @@ function createRateLimitedWarn(windowMs, maxKeys) {
245936
246175
  };
245937
246176
  }
245938
246177
 
245939
- // src/update-check.ts
245940
- var SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/;
245941
- async function fetchLatestPublishedVersion(pkg = "vibedeckx", fetchImpl = fetch) {
245942
- try {
245943
- const response = await fetchImpl(
245944
- `https://registry.npmjs.org/${pkg}/latest`,
245945
- { signal: AbortSignal.timeout(4e3) }
245946
- );
245947
- if (!response.ok) return void 0;
245948
- const body = await response.json();
245949
- return typeof body.version === "string" && parseSemver(body.version) ? body.version : void 0;
245950
- } catch {
245951
- return void 0;
245952
- }
245953
- }
245954
- function parseSemver(version2) {
245955
- const match2 = SEMVER_RE.exec(version2);
245956
- if (!match2) return void 0;
245957
- return {
245958
- core: [match2[1], match2[2], match2[3]],
245959
- prerelease: match2[4]?.split(".")
245960
- };
245961
- }
245962
- function compareNumericStrings(a, b2) {
245963
- if (a.length !== b2.length) return a.length - b2.length;
245964
- return a < b2 ? -1 : a > b2 ? 1 : 0;
245965
- }
245966
- function comparePrereleaseIdentifiers(a, b2) {
245967
- const aNumeric = /^\d+$/.test(a);
245968
- const bNumeric = /^\d+$/.test(b2);
245969
- if (aNumeric && bNumeric) return compareNumericStrings(a, b2);
245970
- if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
245971
- return a < b2 ? -1 : a > b2 ? 1 : 0;
245972
- }
245973
- function compareSemver(a, b2) {
245974
- for (let i = 0; i < 3; i += 1) {
245975
- const compared = compareNumericStrings(a.core[i], b2.core[i]);
245976
- if (compared !== 0) return compared;
245977
- }
245978
- if (!a.prerelease && !b2.prerelease) return 0;
245979
- if (!a.prerelease) return 1;
245980
- if (!b2.prerelease) return -1;
245981
- const length = Math.max(a.prerelease.length, b2.prerelease.length);
245982
- for (let i = 0; i < length; i += 1) {
245983
- const aId = a.prerelease[i];
245984
- const bId = b2.prerelease[i];
245985
- if (aId === void 0) return -1;
245986
- if (bId === void 0) return 1;
245987
- const compared = comparePrereleaseIdentifiers(aId, bId);
245988
- if (compared !== 0) return compared;
245989
- }
245990
- return 0;
245991
- }
245992
- function compareVersionStrings(a, b2) {
245993
- const parsedA = parseSemver(a);
245994
- const parsedB = parseSemver(b2);
245995
- if (!parsedA || !parsedB) return void 0;
245996
- return compareSemver(parsedA, parsedB);
245997
- }
245998
- function compareUpdateStatus(current, latest) {
245999
- if (latest === void 0) return "unknown";
246000
- const currentParsed = parseSemver(current);
246001
- const latestParsed = parseSemver(latest);
246002
- if (!currentParsed || !latestParsed) return "unknown";
246003
- return compareSemver(latestParsed, currentParsed) > 0 ? "update-available" : "up-to-date";
246004
- }
246005
-
246006
246178
  // src/routes/reverse-connect-routes.ts
246007
246179
  function extractReportedVersion(frame) {
246008
246180
  if (typeof frame.version !== "string" || frame.version.length === 0) return void 0;
@@ -246012,7 +246184,7 @@ function extractReportedVersion(frame) {
246012
246184
  var MACHINE_HANDSHAKE_TIMEOUT_MS = 5e3;
246013
246185
  var REJECT_LOG_WINDOW_MS = 3e4;
246014
246186
  var REJECT_LOG_MAX_KEYS = 500;
246015
- var routes24 = async (fastify2) => {
246187
+ var routes25 = async (fastify2) => {
246016
246188
  const logRejectedUpgrade = createRateLimitedWarn(REJECT_LOG_WINDOW_MS, REJECT_LOG_MAX_KEYS);
246017
246189
  fastify2.get("/api/reverse-connect/identity", async (req, reply) => {
246018
246190
  const token = req.headers["x-vibedeckx-connect-token"];
@@ -246059,7 +246231,11 @@ var routes24 = async (fastify2) => {
246059
246231
  console.log(`[ReverseConnect] Worker ${serverId} reported no version (pre-reporting release)`);
246060
246232
  return;
246061
246233
  }
246234
+ const previous = (await fastify2.storage.remoteServers.getById(serverId))?.worker_version;
246062
246235
  await fastify2.storage.remoteServers.updateWorkerVersion(serverId, reported.version, reported.capabilities);
246236
+ if (previous !== reported.version) {
246237
+ console.log(`[ReverseConnect] Worker ${serverId} version ${previous ?? "unreported"} \u2192 ${reported.version}`);
246238
+ }
246063
246239
  const cmp = compareVersionStrings(reported.version, MIN_WORKER_VERSION);
246064
246240
  if (cmp !== void 0 && cmp < 0) {
246065
246241
  console.warn(
@@ -246142,10 +246318,10 @@ var routes24 = async (fastify2) => {
246142
246318
  );
246143
246319
  });
246144
246320
  };
246145
- var reverse_connect_routes_default = (0, import_fastify_plugin25.default)(routes24, { name: "reverse-connect-routes" });
246321
+ var reverse_connect_routes_default = (0, import_fastify_plugin26.default)(routes25, { name: "reverse-connect-routes" });
246146
246322
 
246147
246323
  // src/routes/event-routes.ts
246148
- var import_fastify_plugin26 = __toESM(require_plugin2(), 1);
246324
+ var import_fastify_plugin27 = __toESM(require_plugin2(), 1);
246149
246325
  function toWireEvent(event) {
246150
246326
  if (event.type === "executor:stopped" && (event.tailOutput !== void 0 || event.finalResult !== void 0)) {
246151
246327
  const { tailOutput: _tailOutput, finalResult: _finalResult, ...rest } = event;
@@ -246153,31 +246329,28 @@ function toWireEvent(event) {
246153
246329
  }
246154
246330
  return event;
246155
246331
  }
246156
- var routes25 = async (fastify2) => {
246332
+ var routes26 = async (fastify2) => {
246157
246333
  fastify2.get("/api/events", async (req, reply) => {
246158
246334
  let userId = null;
246159
246335
  if (fastify2.authEnabled) {
246160
- const apiKey = req.headers["x-vibedeckx-api-key"];
246161
- if (!(process.env.VIBEDECKX_API_KEY && apiKey)) {
246162
- const token = req.query.token;
246163
- if (!token) {
246164
- return reply.code(401).send({ error: "Unauthorized" });
246165
- }
246166
- try {
246167
- const { verifyToken: verifyToken3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
246168
- const payload = await verifyToken3(token, {
246169
- secretKey: process.env.CLERK_SECRET_KEY,
246170
- clockSkewInMs: CLERK_CLOCK_SKEW_MS
246171
- });
246172
- if (!payload.sub) {
246173
- return reply.code(401).send({ error: "Unauthorized" });
246174
- }
246175
- userId = payload.sub;
246176
- } catch (err) {
246177
- const reason = err?.reason;
246178
- if (reason) console.log(`[EventsSSE] token verification failed: ${reason}`);
246336
+ const token = req.query.token;
246337
+ if (!token) {
246338
+ return reply.code(401).send({ error: "Unauthorized" });
246339
+ }
246340
+ try {
246341
+ const { verifyToken: verifyToken3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
246342
+ const payload = await verifyToken3(token, {
246343
+ secretKey: process.env.CLERK_SECRET_KEY,
246344
+ clockSkewInMs: CLERK_CLOCK_SKEW_MS
246345
+ });
246346
+ if (!payload.sub) {
246179
246347
  return reply.code(401).send({ error: "Unauthorized" });
246180
246348
  }
246349
+ userId = payload.sub;
246350
+ } catch (err) {
246351
+ const reason = err?.reason;
246352
+ if (reason) console.log(`[EventsSSE] token verification failed: ${reason}`);
246353
+ return reply.code(401).send({ error: "Unauthorized" });
246181
246354
  }
246182
246355
  }
246183
246356
  reply.raw.writeHead(200, {
@@ -246211,10 +246384,10 @@ var routes25 = async (fastify2) => {
246211
246384
  await reply;
246212
246385
  });
246213
246386
  };
246214
- var event_routes_default = (0, import_fastify_plugin26.default)(routes25, { name: "event-routes" });
246387
+ var event_routes_default = (0, import_fastify_plugin27.default)(routes26, { name: "event-routes" });
246215
246388
 
246216
246389
  // src/routes/terminal-routes.ts
246217
- var import_fastify_plugin27 = __toESM(require_plugin2(), 1);
246390
+ var import_fastify_plugin28 = __toESM(require_plugin2(), 1);
246218
246391
  import path13 from "path";
246219
246392
 
246220
246393
  // src/utils/path-project.ts
@@ -246242,7 +246415,7 @@ async function getRemoteConfig5(fastify2, project, remoteServerId) {
246242
246415
  remotePath: target.remote_path
246243
246416
  };
246244
246417
  }
246245
- var routes26 = async (fastify2) => {
246418
+ var routes27 = async (fastify2) => {
246246
246419
  fastify2.post("/api/path/terminals", async (req, reply) => {
246247
246420
  const { path: projectPath, branch } = req.body;
246248
246421
  if (!projectPath) {
@@ -246418,11 +246591,11 @@ var routes26 = async (fastify2) => {
246418
246591
  }
246419
246592
  );
246420
246593
  };
246421
- var terminal_routes_default = (0, import_fastify_plugin27.default)(routes26, { name: "terminal-routes" });
246594
+ var terminal_routes_default = (0, import_fastify_plugin28.default)(routes27, { name: "terminal-routes" });
246422
246595
 
246423
246596
  // src/routes/browser-routes.ts
246424
- var import_fastify_plugin28 = __toESM(require_plugin2(), 1);
246425
- var routes27 = async (fastify2) => {
246597
+ var import_fastify_plugin29 = __toESM(require_plugin2(), 1);
246598
+ var routes28 = async (fastify2) => {
246426
246599
  const ensureProjectAccess = async (req, reply) => {
246427
246600
  const userId = requireUserFacingUserId(req, reply);
246428
246601
  if (userId === null) return null;
@@ -246493,10 +246666,10 @@ var routes27 = async (fastify2) => {
246493
246666
  return reply.code(200).send({ ok: true });
246494
246667
  });
246495
246668
  };
246496
- var browser_routes_default = (0, import_fastify_plugin28.default)(routes27, { name: "browser-routes" });
246669
+ var browser_routes_default = (0, import_fastify_plugin29.default)(routes28, { name: "browser-routes" });
246497
246670
 
246498
246671
  // src/routes/browser-proxy-routes.ts
246499
- var import_fastify_plugin29 = __toESM(require_plugin2(), 1);
246672
+ var import_fastify_plugin30 = __toESM(require_plugin2(), 1);
246500
246673
  import { randomUUID as randomUUID21 } from "crypto";
246501
246674
 
246502
246675
  // src/utils/ssrf-guard.ts
@@ -246992,7 +247165,7 @@ function generateInjectedScript(projectId, targetOrigin, proxyWsPrefix) {
246992
247165
  })();
246993
247166
  </script>`;
246994
247167
  }
246995
- var routes28 = async (fastify2) => {
247168
+ var routes29 = async (fastify2) => {
246996
247169
  fastify2.get("/api/projects/:id/browser/proxy/*", async (req, reply) => {
246997
247170
  const userId = requireUserFacingOrTrustedProxyUserId(req, reply);
246998
247171
  if (userId === null) return;
@@ -247061,7 +247234,7 @@ var routes28 = async (fastify2) => {
247061
247234
  const { id: projectId } = req.params;
247062
247235
  const principal = await authenticateWs(fastify2.authEnabled, req.query, socket);
247063
247236
  if (!principal) return;
247064
- const projectOwner = principal.kind === "api_key" ? void 0 : principal.userId ?? "local";
247237
+ const projectOwner = principal.userId ?? "local";
247065
247238
  if (!await fastify2.storage.projects.getById(projectId, projectOwner)) {
247066
247239
  try {
247067
247240
  socket.send(JSON.stringify({ error: "Forbidden" }));
@@ -247164,10 +247337,10 @@ var routes28 = async (fastify2) => {
247164
247337
  });
247165
247338
  });
247166
247339
  };
247167
- var browser_proxy_routes_default = (0, import_fastify_plugin29.default)(routes28, { name: "browser-proxy-routes" });
247340
+ var browser_proxy_routes_default = (0, import_fastify_plugin30.default)(routes29, { name: "browser-proxy-routes" });
247168
247341
 
247169
247342
  // src/routes/cross-remote-target-routes.ts
247170
- var import_fastify_plugin30 = __toESM(require_plugin2(), 1);
247343
+ var import_fastify_plugin31 = __toESM(require_plugin2(), 1);
247171
247344
  import path14 from "path";
247172
247345
  import { promises as fs3 } from "fs";
247173
247346
 
@@ -247247,7 +247420,7 @@ var clampTimeoutMs = (timeoutSec) => {
247247
247420
  const requested = typeof timeoutSec === "number" && Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : DEFAULT_TIMEOUT_SEC;
247248
247421
  return Math.min(requested, MAX_TIMEOUT_SEC) * 1e3;
247249
247422
  };
247250
- var routes29 = async (fastify2) => {
247423
+ var routes30 = async (fastify2) => {
247251
247424
  fastify2.post(
247252
247425
  "/api/path/cross-remote/exec",
247253
247426
  async (request, reply) => {
@@ -247355,10 +247528,10 @@ var routes29 = async (fastify2) => {
247355
247528
  return reply.send(result);
247356
247529
  });
247357
247530
  };
247358
- var cross_remote_target_routes_default = (0, import_fastify_plugin30.default)(routes29, { name: "cross-remote-target-routes" });
247531
+ var cross_remote_target_routes_default = (0, import_fastify_plugin31.default)(routes30, { name: "cross-remote-target-routes" });
247359
247532
 
247360
247533
  // src/routes/cross-remote-mcp-routes.ts
247361
- var import_fastify_plugin31 = __toESM(require_plugin2(), 1);
247534
+ var import_fastify_plugin32 = __toESM(require_plugin2(), 1);
247362
247535
  var PROTOCOL_VERSION = "2024-11-05";
247363
247536
  var AUDIT_ARGS_MAX = 1024;
247364
247537
  var NOT_ACCESSIBLE = "remote not found or not accessible";
@@ -247461,7 +247634,7 @@ var textResult = (text2, isError = false) => ({
247461
247634
  content: [{ type: "text", text: text2 }],
247462
247635
  ...isError ? { isError: true } : {}
247463
247636
  });
247464
- var routes30 = async (fastify2) => {
247637
+ var routes31 = async (fastify2) => {
247465
247638
  const guard = new SessionConcurrencyGuard();
247466
247639
  let cachedSecret;
247467
247640
  const getSecret = () => {
@@ -247582,10 +247755,10 @@ var routes30 = async (fastify2) => {
247582
247755
  }
247583
247756
  });
247584
247757
  };
247585
- var cross_remote_mcp_routes_default = (0, import_fastify_plugin31.default)(routes30, { name: "cross-remote-mcp-routes" });
247758
+ var cross_remote_mcp_routes_default = (0, import_fastify_plugin32.default)(routes31, { name: "cross-remote-mcp-routes" });
247586
247759
 
247587
247760
  // src/routes/schedule-routes.ts
247588
- var import_fastify_plugin32 = __toESM(require_plugin2(), 1);
247761
+ var import_fastify_plugin33 = __toESM(require_plugin2(), 1);
247589
247762
  import { randomUUID as randomUUID22 } from "crypto";
247590
247763
  import path15 from "path";
247591
247764
  var RUN_TYPES = ["command", "prompt"];
@@ -247604,7 +247777,7 @@ function validateResolved(b2) {
247604
247777
  if (cronError) return `Invalid cron expression: ${cronError}`;
247605
247778
  return null;
247606
247779
  }
247607
- var routes31 = async (fastify2) => {
247780
+ var routes32 = async (fastify2) => {
247608
247781
  const sendTerminalManualOutcome = (reply, request, replay) => {
247609
247782
  if (!request.terminalStatus || request.terminalResponseStatus === null) return null;
247610
247783
  const common = { runId: request.runId, status: request.terminalStatus, durable: true, replay };
@@ -247835,10 +248008,10 @@ var routes31 = async (fastify2) => {
247835
248008
  }
247836
248009
  );
247837
248010
  };
247838
- var schedule_routes_default = (0, import_fastify_plugin32.default)(routes31, { name: "schedule-routes" });
248011
+ var schedule_routes_default = (0, import_fastify_plugin33.default)(routes32, { name: "schedule-routes" });
247839
248012
 
247840
248013
  // src/search/catalog.ts
247841
- var parseDbTimestamp4 = (ts) => {
248014
+ var parseDbTimestamp5 = (ts) => {
247842
248015
  if (!ts) return null;
247843
248016
  const ms = Date.parse(ts.replace(" ", "T") + "Z");
247844
248017
  return Number.isNaN(ms) ? null : ms;
@@ -247860,7 +248033,7 @@ async function buildSearchCatalog(deps, projectId, projectPath) {
247860
248033
  id: s3.id,
247861
248034
  branch: s3.branch === "" ? null : s3.branch,
247862
248035
  title: s3.title ?? null,
247863
- lastActiveAt: s3.last_user_message_at ?? parseDbTimestamp4(s3.updated_at),
248036
+ lastActiveAt: s3.last_user_message_at ?? parseDbTimestamp5(s3.updated_at),
247864
248037
  favoritedAt: s3.favorited_at ?? null,
247865
248038
  entryCount,
247866
248039
  status: s3.status,
@@ -248127,10 +248300,10 @@ var searchRoutes = async (fastify2) => {
248127
248300
  var search_routes_default = searchRoutes;
248128
248301
 
248129
248302
  // src/routes/notification-routes.ts
248130
- var import_fastify_plugin33 = __toESM(require_plugin2(), 1);
248303
+ var import_fastify_plugin34 = __toESM(require_plugin2(), 1);
248131
248304
  var MAX_LIMIT = 500;
248132
248305
  var DEFAULT_LIMIT = 100;
248133
- var routes32 = async (fastify2) => {
248306
+ var routes33 = async (fastify2) => {
248134
248307
  fastify2.get(
248135
248308
  "/api/notifications",
248136
248309
  async (req, reply) => {
@@ -248162,7 +248335,7 @@ var routes32 = async (fastify2) => {
248162
248335
  return reply.code(200).send({ ok: true });
248163
248336
  });
248164
248337
  };
248165
- var notification_routes_default = (0, import_fastify_plugin33.default)(routes32, { name: "notification-routes" });
248338
+ var notification_routes_default = (0, import_fastify_plugin34.default)(routes33, { name: "notification-routes" });
248166
248339
 
248167
248340
  // ../../node_modules/.pnpm/@clerk+fastify@3.1.2_fastify@5.7.2_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@clerk/fastify/dist/chunk-RY4T2JMQ.mjs
248168
248341
  import { Readable as Readable2 } from "stream";
@@ -248214,7 +248387,7 @@ var requestToProxyRequest = (req) => {
248214
248387
 
248215
248388
  // ../../node_modules/.pnpm/@clerk+fastify@3.1.2_fastify@5.7.2_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@clerk/fastify/dist/index.mjs
248216
248389
  init_dist();
248217
- var import_fastify_plugin34 = __toESM(require_plugin2(), 1);
248390
+ var import_fastify_plugin35 = __toESM(require_plugin2(), 1);
248218
248391
 
248219
248392
  // ../../node_modules/.pnpm/@clerk+backend@3.2.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@clerk/backend/dist/internal.mjs
248220
248393
  init_chunk_JW73PKED();
@@ -248502,7 +248675,7 @@ var plugin = (instance, opts, done) => {
248502
248675
  instance.addHook(hookName, withClerkMiddleware(opts));
248503
248676
  done();
248504
248677
  };
248505
- var clerkPlugin = (0, import_fastify_plugin34.default)(plugin, {
248678
+ var clerkPlugin = (0, import_fastify_plugin35.default)(plugin, {
248506
248679
  name: "@clerk/fastify",
248507
248680
  fastify: "5.x"
248508
248681
  });
@@ -249250,12 +249423,10 @@ async function shutdownLogging() {
249250
249423
  }
249251
249424
 
249252
249425
  // src/server.ts
249253
- var API_KEY = process.env.VIBEDECKX_API_KEY;
249426
+ var API_KEY2 = process.env.VIBEDECKX_API_KEY || void 0;
249254
249427
  function requireAuth(req, reply) {
249255
249428
  const server = req.server;
249256
249429
  if (!server.authEnabled) return void 0;
249257
- const apiKeyHeader = req.headers["x-vibedeckx-api-key"];
249258
- if (API_KEY && apiKeyHeader) return void 0;
249259
249430
  try {
249260
249431
  const { userId } = getAuth(req);
249261
249432
  if (!userId) {
@@ -249342,19 +249513,12 @@ var createServer = async (opts) => {
249342
249513
  done();
249343
249514
  });
249344
249515
  server.addHook("onRequest", (req, reply, done) => {
249345
- if (!API_KEY) return done();
249346
- if (!req.url.startsWith("/api/")) return done();
249516
+ if (!API_KEY2) return done();
249517
+ if (!req.url.startsWith("/api/admin/")) return done();
249347
249518
  if (req.method === "OPTIONS") return done();
249348
- const pathname = req.url.split("?")[0];
249349
- if (pathname === "/api/config" || pathname === "/api/reverse-connect" || pathname === "/api/reverse-connect/identity") {
249350
- return done();
249351
- }
249352
249519
  const providedKey = req.headers["x-vibedeckx-api-key"] || req.query?.apiKey;
249353
- if (!providedKey && authEnabled) {
249354
- return done();
249355
- }
249356
- if (providedKey !== API_KEY) {
249357
- return reply.code(401).send({ error: "Unauthorized" });
249520
+ if (providedKey !== API_KEY2) {
249521
+ return reply.code(404).send({ error: "Not found" });
249358
249522
  }
249359
249523
  done();
249360
249524
  });
@@ -249430,6 +249594,7 @@ var createServer = async (opts) => {
249430
249594
  server.register(reverse_connect_routes_default);
249431
249595
  server.register(project_routes_default);
249432
249596
  server.register(remote_server_routes_default);
249597
+ server.register(worker_stats_routes_default);
249433
249598
  server.register(project_remote_routes_default);
249434
249599
  server.register(search_routes_default);
249435
249600
  server.register(executor_group_routes_default);
@@ -249919,14 +250084,10 @@ var ReverseConnectClient = class {
249919
250084
  });
249920
250085
  body = await response.text();
249921
250086
  } else {
249922
- const injectHeaders = { ...frame.headers ?? {} };
249923
- if (process.env.VIBEDECKX_API_KEY) {
249924
- injectHeaders["x-vibedeckx-api-key"] = process.env.VIBEDECKX_API_KEY;
249925
- }
249926
250087
  const response = await this.localServer.inject({
249927
250088
  method: frame.method,
249928
250089
  url: frame.path,
249929
- headers: injectHeaders,
250090
+ headers: frame.headers ?? {},
249930
250091
  payload: frame.body
249931
250092
  });
249932
250093
  status = response.statusCode;
@@ -249966,11 +250127,7 @@ var ReverseConnectClient = class {
249966
250127
  }
249967
250128
  }
249968
250129
  handleWsOpen(frame) {
249969
- const queryParts = [
249970
- ...frame.query ? [frame.query] : [],
249971
- ...process.env.VIBEDECKX_API_KEY ? [`apiKey=${encodeURIComponent(process.env.VIBEDECKX_API_KEY)}`] : []
249972
- ];
249973
- const wsUrl = `ws://127.0.0.1:${this.localPort}${frame.path}${queryParts.length ? `?${queryParts.join("&")}` : ""}`;
250130
+ const wsUrl = `ws://127.0.0.1:${this.localPort}${frame.path}${frame.query ? `?${frame.query}` : ""}`;
249974
250131
  console.log(`[ReverseClient] Opening local WS channel ${frame.channelId} \u2192 ${frame.path}`);
249975
250132
  const localWs = new wrapper_default(wsUrl);
249976
250133
  localWs.on("open", () => {
@@ -251477,7 +251634,7 @@ var startCommand = buildCommand({
251477
251634
  host: {
251478
251635
  kind: "parsed",
251479
251636
  parse: String,
251480
- brief: "Network interface to bind (default: 127.0.0.1, loopback only). Use 0.0.0.0 to expose on all interfaces \u2014 only do so behind --auth, VIBEDECKX_API_KEY, or a trusted tunnel/proxy.",
251637
+ brief: "Network interface to bind (default: 127.0.0.1, loopback only). Use 0.0.0.0 to expose on all interfaces \u2014 only do so behind --auth or an authenticating proxy.",
251481
251638
  optional: true
251482
251639
  },
251483
251640
  auth: {
@@ -251548,11 +251705,16 @@ var startCommand = buildCommand({
251548
251705
  const noLocalProjects = flags["no-local-projects"] ?? false;
251549
251706
  const tls = loadTLSOptions(flags);
251550
251707
  const isLoopbackHost = host === "127.0.0.1" || host === "::1" || host === "localhost";
251551
- if (!isLoopbackHost && !authEnabled && !process.env.VIBEDECKX_API_KEY) {
251708
+ if (!isLoopbackHost && !authEnabled) {
251552
251709
  console.warn(
251553
251710
  `Warning: binding to ${host} exposes vibedeckx on the network with no authentication.
251554
- Anyone who can reach this host can run commands via the executor API. Enable --auth or
251555
- set VIBEDECKX_API_KEY, or keep the default loopback bind (127.0.0.1) behind a trusted tunnel/proxy.`
251711
+ Anyone who can reach this host can run commands via the executor API. Enable --auth, or put
251712
+ an authenticating proxy in front, or keep the default loopback bind (127.0.0.1).`
251713
+ );
251714
+ }
251715
+ if (process.env.VIBEDECKX_API_KEY) {
251716
+ console.log(
251717
+ "VIBEDECKX_API_KEY is set: it authenticates operator-only endpoints (/api/admin/*).\nIt does not lock the rest of the API and is not user authentication \u2014 use --auth or a\nproxy in front for that."
251556
251718
  );
251557
251719
  }
251558
251720
  console.log(`Starting vibedeckx${tls ? " (HTTPS)" : ""}...`);
@@ -251828,7 +251990,7 @@ var connectRoutes = buildRouteMap({
251828
251990
  defaultCommand: "run",
251829
251991
  docs: { brief: "Connect to a Vibedeckx server as an inbound node" }
251830
251992
  });
251831
- var routes33 = buildRouteMap({
251993
+ var routes34 = buildRouteMap({
251832
251994
  routes: {
251833
251995
  start: startCommand,
251834
251996
  connect: connectRoutes
@@ -251838,7 +252000,7 @@ var routes33 = buildRouteMap({
251838
252000
  brief: "Vibedeckx - AI-powered app generator"
251839
252001
  }
251840
252002
  });
251841
- var program = buildApplication(routes33, {
252003
+ var program = buildApplication(routes34, {
251842
252004
  name: "vibedeckx",
251843
252005
  versionInfo: {
251844
252006
  currentVersion: readPackageVersion()