@vibedeckx/linux-x64 0.3.30 → 0.3.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +327 -184
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -188505,6 +188505,10 @@ var createWorkflowRunRepos = (kdb) => ({
188505
188505
  const row = await kdb.selectFrom("workflow_runs").selectAll().where("source_session_id", "=", sourceSessionId).where("status", "=", "completed").where("reviewer_session_id", "is not", null).orderBy("created_at", "desc").orderBy(sql`rowid`, "desc").executeTakeFirst();
188506
188506
  return row ? asRun(row) : void 0;
188507
188507
  },
188508
+ listReviewedSourceSessions: async (projectId, branch) => {
188509
+ const rows = await kdb.selectFrom("workflow_runs").select("source_session_id").distinct().where("project_id", "=", projectId).where("branch", "is", branch).where("status", "=", "completed").where("reviewer_session_id", "is not", null).execute();
188510
+ return rows.map((r) => r.source_session_id);
188511
+ },
188508
188512
  update: async (id, patch) => {
188509
188513
  if (Object.keys(patch).length > 0) {
188510
188514
  await kdb.updateTable("workflow_runs").set({ ...patch, updated_at: sql`datetime('now')` }).where("id", "=", id).execute();
@@ -205224,6 +205228,12 @@ var initializeSchema = (db) => {
205224
205228
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
205225
205229
  );
205226
205230
 
205231
+ -- getActive (panel poll, every 5s per open workspace) and
205232
+ -- listReviewedSourceSessions (same request) both scope by project+branch
205233
+ -- and differ only in the status filter.
205234
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_project_branch_status
205235
+ ON workflow_runs(project_id, branch, status);
205236
+
205227
205237
  CREATE TABLE IF NOT EXISTS turn_snapshots (
205228
205238
  session_id TEXT NOT NULL,
205229
205239
  turn_end_index INTEGER NOT NULL,
@@ -207854,6 +207864,176 @@ function getAllProviders() {
207854
207864
  return Array.from(providers.values());
207855
207865
  }
207856
207866
 
207867
+ // src/utils/cross-remote-token.ts
207868
+ import { createHmac as createHmac2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
207869
+ var CROSS_REMOTE_SECRET_SETTING = "cross_remote_token_secret";
207870
+ var CROSS_REMOTE_TOKEN_TTL_MS = 7 * 864e5;
207871
+ var sign2 = (secret, body) => createHmac2("sha256", secret).update(body).digest("base64url");
207872
+ function signRemoteMcpHandle(secret, payload, nowMs, ttlMs = CROSS_REMOTE_TOKEN_TTL_MS) {
207873
+ const wire = {
207874
+ u: payload.userId,
207875
+ s: payload.sessionId,
207876
+ r: payload.remoteId,
207877
+ h: payload.workerHandle,
207878
+ n: payload.serverLabel,
207879
+ exp: nowMs + ttlMs
207880
+ };
207881
+ const body = Buffer.from(JSON.stringify(wire)).toString("base64url");
207882
+ return `mcp.${body}.${sign2(secret, `mcp:${body}`)}`;
207883
+ }
207884
+ function verifyRemoteMcpHandle(secret, handle, nowMs) {
207885
+ const parts = handle.split(".");
207886
+ if (parts.length !== 3) return null;
207887
+ const [prefix, body, providedSig] = parts;
207888
+ if (prefix !== "mcp" || !body || !providedSig) return null;
207889
+ const expectedSig = sign2(secret, `mcp:${body}`);
207890
+ const provided = Buffer.from(providedSig);
207891
+ const expected = Buffer.from(expectedSig);
207892
+ if (provided.length !== expected.length || !timingSafeEqual2(provided, expected)) return null;
207893
+ let wire;
207894
+ try {
207895
+ wire = JSON.parse(Buffer.from(body, "base64url").toString());
207896
+ } catch {
207897
+ return null;
207898
+ }
207899
+ if (![wire.u, wire.s, wire.r, wire.h, wire.n].every((v2) => typeof v2 === "string" && v2.length > 0)) return null;
207900
+ if (typeof wire.exp !== "number" || nowMs >= wire.exp) return null;
207901
+ return { userId: wire.u, sessionId: wire.s, remoteId: wire.r, workerHandle: wire.h, serverLabel: wire.n };
207902
+ }
207903
+ function signCrossRemoteToken(secret, payload, nowMs, ttlMs = CROSS_REMOTE_TOKEN_TTL_MS) {
207904
+ const wire = {
207905
+ u: payload.userId,
207906
+ s: payload.sessionId,
207907
+ src: payload.sourceRemoteServerId,
207908
+ exp: nowMs + ttlMs
207909
+ };
207910
+ const body = Buffer.from(JSON.stringify(wire)).toString("base64url");
207911
+ return `${body}.${sign2(secret, body)}`;
207912
+ }
207913
+ function verifyCrossRemoteTokenDetailed(secret, token, nowMs) {
207914
+ const invalid = { status: "invalid" };
207915
+ const parts = token.split(".");
207916
+ if (parts.length !== 2) return invalid;
207917
+ const [body, providedSig] = parts;
207918
+ if (!body || !providedSig) return invalid;
207919
+ const expectedSig = sign2(secret, body);
207920
+ const provided = Buffer.from(providedSig);
207921
+ const expected = Buffer.from(expectedSig);
207922
+ if (provided.length !== expected.length) return invalid;
207923
+ if (!timingSafeEqual2(provided, expected)) return invalid;
207924
+ let wire;
207925
+ try {
207926
+ wire = JSON.parse(Buffer.from(body, "base64url").toString());
207927
+ } catch {
207928
+ return invalid;
207929
+ }
207930
+ if (typeof wire.u !== "string" || typeof wire.s !== "string" || typeof wire.exp !== "number") return invalid;
207931
+ if (wire.src !== null && typeof wire.src !== "string") return invalid;
207932
+ if (!wire.u || !wire.s) return invalid;
207933
+ const payload = { userId: wire.u, sessionId: wire.s, sourceRemoteServerId: wire.src };
207934
+ if (nowMs >= wire.exp) return { status: "expired", payload, exp: wire.exp };
207935
+ return { status: "ok", payload };
207936
+ }
207937
+ async function getCrossRemoteSecret(storage2) {
207938
+ return storage2.settings.getOrCreate(
207939
+ CROSS_REMOTE_SECRET_SETTING,
207940
+ () => randomBytes2(32).toString("hex")
207941
+ );
207942
+ }
207943
+
207944
+ // src/cross-remote-access.ts
207945
+ var CROSS_REMOTE_MCP_PATH = "/api/cross-remote-mcp";
207946
+ var TOOL_TIERS = {
207947
+ remote_read_file: "read",
207948
+ remote_list_dir: "read",
207949
+ remote_stat_path: "read",
207950
+ remote_process_list: "read",
207951
+ remote_bash: "exec",
207952
+ remote_mcp_open: "exec",
207953
+ remote_mcp_list_tools: "exec",
207954
+ remote_mcp_call: "exec",
207955
+ remote_mcp_ping: "exec",
207956
+ remote_mcp_close: "exec"
207957
+ };
207958
+ var MAX_IN_FLIGHT_PER_SESSION = 4;
207959
+ var REMOTE_MCP_CAPABILITIES = [
207960
+ "http:POST /api/path/cross-remote/mcp/open",
207961
+ "http:POST /api/path/cross-remote/mcp/list-tools",
207962
+ "http:POST /api/path/cross-remote/mcp/call",
207963
+ "http:POST /api/path/cross-remote/mcp/ping",
207964
+ "http:POST /api/path/cross-remote/mcp/close"
207965
+ ];
207966
+ var supportsRemoteMcpBroker = (server) => REMOTE_MCP_CAPABILITIES.every((capability) => server.worker_capabilities?.includes(capability));
207967
+ var tierSatisfies = (granted, required2) => granted === "exec" || granted === "read" && required2 === "read";
207968
+ var isOnline = (deps, server) => deps.reverseConnectManager.isConnected(server.id);
207969
+ function isSessionUsable(deps, sessionId) {
207970
+ if (sessionId.startsWith("remote-")) return deps.remoteSessionMap.has(sessionId);
207971
+ return deps.agentSessionManager.getSessionProcessAlive(sessionId);
207972
+ }
207973
+ async function resolveTarget(deps, payload, targetRemoteId, requiredTier) {
207974
+ if (payload.sourceRemoteServerId && payload.sourceRemoteServerId === targetRemoteId) {
207975
+ return { ok: false, reason: "not_accessible" };
207976
+ }
207977
+ const server = await deps.storage.remoteServers.getById(targetRemoteId, payload.userId);
207978
+ if (!server) return { ok: false, reason: "not_accessible" };
207979
+ if (!tierSatisfies(server.cross_remote_access, requiredTier)) {
207980
+ return { ok: false, reason: "not_accessible" };
207981
+ }
207982
+ if (!isOnline(deps, server)) return { ok: false, reason: "offline" };
207983
+ return { ok: true, server };
207984
+ }
207985
+ async function listAccessibleRemotes(deps, payload) {
207986
+ const servers = await deps.storage.remoteServers.getAll(payload.userId);
207987
+ return servers.filter((s3) => s3.cross_remote_access !== "off").filter((s3) => s3.id !== payload.sourceRemoteServerId).map((s3) => ({
207988
+ id: s3.id,
207989
+ name: s3.name,
207990
+ access: s3.cross_remote_access,
207991
+ online: isOnline(deps, s3),
207992
+ mcp_broker_supported: supportsRemoteMcpBroker(s3)
207993
+ }));
207994
+ }
207995
+ var SessionConcurrencyGuard = class {
207996
+ constructor(maxInFlight = MAX_IN_FLIGHT_PER_SESSION) {
207997
+ this.maxInFlight = maxInFlight;
207998
+ }
207999
+ maxInFlight;
208000
+ inFlight = /* @__PURE__ */ new Map();
208001
+ acquire(sessionId) {
208002
+ const current = this.inFlight.get(sessionId) ?? 0;
208003
+ if (current >= this.maxInFlight) return false;
208004
+ this.inFlight.set(sessionId, current + 1);
208005
+ return true;
208006
+ }
208007
+ release(sessionId) {
208008
+ const current = this.inFlight.get(sessionId) ?? 0;
208009
+ if (current <= 1) this.inFlight.delete(sessionId);
208010
+ else this.inFlight.set(sessionId, current - 1);
208011
+ }
208012
+ };
208013
+
208014
+ // src/cross-remote-mcp-config.ts
208015
+ function crossRemoteMcpEnabled() {
208016
+ return !!process.env.VIBEDECKX_PUBLIC_URL?.trim();
208017
+ }
208018
+ async function mintCrossRemoteMcpConfig(deps, args) {
208019
+ const baseUrl = process.env.VIBEDECKX_PUBLIC_URL?.trim();
208020
+ if (!baseUrl) return void 0;
208021
+ const { userId } = args;
208022
+ if (!userId) return void 0;
208023
+ const servers = await deps.storage.remoteServers.getAll(userId);
208024
+ const hasTarget = servers.some(
208025
+ (s3) => s3.cross_remote_access !== "off" && s3.id !== args.sourceRemoteServerId
208026
+ );
208027
+ if (!hasTarget) return void 0;
208028
+ const secret = await getCrossRemoteSecret(deps.storage);
208029
+ const token = signCrossRemoteToken(
208030
+ secret,
208031
+ { userId, sessionId: args.sessionId, sourceRemoteServerId: args.sourceRemoteServerId },
208032
+ Date.now()
208033
+ );
208034
+ return { url: `${baseUrl.replace(/\/+$/, "")}${CROSS_REMOTE_MCP_PATH}`, token };
208035
+ }
208036
+
207857
208037
  // src/conversation-patch.ts
207858
208038
  var ConversationPatch = {
207859
208039
  /**
@@ -230222,6 +230402,7 @@ var AgentSessionManager = class {
230222
230402
  skipDb,
230223
230403
  permissionMode,
230224
230404
  crossRemoteMcp: opts.crossRemoteMcp,
230405
+ userId: opts.userId && opts.userId !== "local" ? opts.userId : void 0,
230225
230406
  agentType,
230226
230407
  model,
230227
230408
  completion: new TurnCompletionLedger(this.parkTimeoutMs),
@@ -230276,6 +230457,18 @@ var AgentSessionManager = class {
230276
230457
  await this.restartSession(session.id, projectPath);
230277
230458
  return session.id;
230278
230459
  }
230460
+ /**
230461
+ * Replace the session's cross-remote MCP config for its NEXT spawn. Worker
230462
+ * side of the hub's per-message token refresh: a live process keeps the
230463
+ * token it was spawned with (baked into --mcp-config), but the next wake
230464
+ * picks this one up instead of the possibly-expired original.
230465
+ */
230466
+ updateCrossRemoteMcp(sessionId, config2) {
230467
+ const session = this.sessions.get(sessionId);
230468
+ if (!session) return false;
230469
+ session.crossRemoteMcp = config2;
230470
+ return true;
230471
+ }
230279
230472
  /**
230280
230473
  * Kill an agent process and its entire process tree.
230281
230474
  * Uses negative PID to signal the process group (requires detached: true at spawn).
@@ -230322,6 +230515,16 @@ var AgentSessionManager = class {
230322
230515
  console.error(`[AgentSession] Failed to mint session tools MCP config for ${session.id}:`, err);
230323
230516
  return void 0;
230324
230517
  });
230518
+ if (session.userId && crossRemoteMcpEnabled()) {
230519
+ try {
230520
+ session.crossRemoteMcp = await mintCrossRemoteMcpConfig(
230521
+ { storage: this.storage },
230522
+ { userId: session.userId, sessionId: session.id, sourceRemoteServerId: null }
230523
+ );
230524
+ } catch (err) {
230525
+ console.error(`[AgentSession] Cross-remote token re-mint failed for ${session.id}, keeping cached config:`, err);
230526
+ }
230527
+ }
230325
230528
  const config2 = provider.buildSpawnConfig(
230326
230529
  cwd,
230327
230530
  session.permissionMode,
@@ -231092,6 +231295,7 @@ var AgentSessionManager = class {
231092
231295
  async sendUserMessage(sessionId, content, projectPath, userId = "local", opts) {
231093
231296
  const session = this.sessions.get(sessionId);
231094
231297
  if (!session) return false;
231298
+ if (userId && userId !== "local" && !session.userId) session.userId = userId;
231095
231299
  const disposition = this.resolveOutgoingDisposition(sessionId, opts);
231096
231300
  if (session.dormant) {
231097
231301
  if (!projectPath) {
@@ -232213,6 +232417,7 @@ var AgentSessionManager = class {
232213
232417
  turnOpenSince: null,
232214
232418
  turnDisposition: null,
232215
232419
  crossRemoteMcp: opts.crossRemoteMcp,
232420
+ userId: opts.userId && opts.userId !== "local" ? opts.userId : void 0,
232216
232421
  branchedFromSessionId: sourceSessionId,
232217
232422
  branchedFromEntryIndex
232218
232423
  };
@@ -232299,7 +232504,7 @@ import { randomUUID as randomUUID3 } from "crypto";
232299
232504
 
232300
232505
  // src/trace-context.ts
232301
232506
  import { AsyncLocalStorage } from "node:async_hooks";
232302
- import { randomBytes as randomBytes2 } from "node:crypto";
232507
+ import { randomBytes as randomBytes3 } from "node:crypto";
232303
232508
  var VERSION8 = "00";
232304
232509
  var INVALID_VERSION = "ff";
232305
232510
  var ZERO_TRACE_ID = "0".repeat(32);
@@ -232324,10 +232529,10 @@ function formatTraceparent(ctx) {
232324
232529
  return `${VERSION8}-${ctx.traceId}-${ctx.spanId}-${ctx.sampled ? "01" : "00"}`;
232325
232530
  }
232326
232531
  function newTraceId() {
232327
- return randomBytes2(16).toString("hex");
232532
+ return randomBytes3(16).toString("hex");
232328
232533
  }
232329
232534
  function newSpanId() {
232330
- return randomBytes2(8).toString("hex");
232535
+ return randomBytes3(8).toString("hex");
232331
232536
  }
232332
232537
  function newTraceContext(incoming) {
232333
232538
  const parsed = parseTraceparent(incoming);
@@ -232493,6 +232698,13 @@ function mapRemoteRun(run2, remoteServerId, projectId) {
232493
232698
  reviewer_session_id: run2.reviewer_session_id ? `${prefix}${run2.reviewer_session_id}` : null
232494
232699
  };
232495
232700
  }
232701
+ var UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}";
232702
+ var REMOTE_RUN_ID_RE = new RegExp(`^remote-(${UUID_PATTERN})-(${UUID_PATTERN})-(${UUID_PATTERN})$`);
232703
+ function parseRemoteRunId(runId) {
232704
+ const match2 = REMOTE_RUN_ID_RE.exec(runId);
232705
+ if (!match2) return null;
232706
+ return { remoteServerId: match2[1], projectId: match2[2], bareRunId: match2[3] };
232707
+ }
232496
232708
  function mapRemoteReviewerCandidate(candidate, remoteServerId, projectId) {
232497
232709
  if (!candidate?.sessionId) return candidate;
232498
232710
  return {
@@ -232511,171 +232723,6 @@ function runUpdatedFrameForSubscribers(evt) {
232511
232723
  return JSON.stringify({ workflowRunUpdated: evt.run });
232512
232724
  }
232513
232725
 
232514
- // src/utils/cross-remote-token.ts
232515
- import { createHmac as createHmac2, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
232516
- var CROSS_REMOTE_SECRET_SETTING = "cross_remote_token_secret";
232517
- var CROSS_REMOTE_TOKEN_TTL_MS = 864e5;
232518
- var sign2 = (secret, body) => createHmac2("sha256", secret).update(body).digest("base64url");
232519
- function signRemoteMcpHandle(secret, payload, nowMs, ttlMs = CROSS_REMOTE_TOKEN_TTL_MS) {
232520
- const wire = {
232521
- u: payload.userId,
232522
- s: payload.sessionId,
232523
- r: payload.remoteId,
232524
- h: payload.workerHandle,
232525
- n: payload.serverLabel,
232526
- exp: nowMs + ttlMs
232527
- };
232528
- const body = Buffer.from(JSON.stringify(wire)).toString("base64url");
232529
- return `mcp.${body}.${sign2(secret, `mcp:${body}`)}`;
232530
- }
232531
- function verifyRemoteMcpHandle(secret, handle, nowMs) {
232532
- const parts = handle.split(".");
232533
- if (parts.length !== 3) return null;
232534
- const [prefix, body, providedSig] = parts;
232535
- if (prefix !== "mcp" || !body || !providedSig) return null;
232536
- const expectedSig = sign2(secret, `mcp:${body}`);
232537
- const provided = Buffer.from(providedSig);
232538
- const expected = Buffer.from(expectedSig);
232539
- if (provided.length !== expected.length || !timingSafeEqual2(provided, expected)) return null;
232540
- let wire;
232541
- try {
232542
- wire = JSON.parse(Buffer.from(body, "base64url").toString());
232543
- } catch {
232544
- return null;
232545
- }
232546
- if (![wire.u, wire.s, wire.r, wire.h, wire.n].every((v2) => typeof v2 === "string" && v2.length > 0)) return null;
232547
- if (typeof wire.exp !== "number" || nowMs >= wire.exp) return null;
232548
- return { userId: wire.u, sessionId: wire.s, remoteId: wire.r, workerHandle: wire.h, serverLabel: wire.n };
232549
- }
232550
- function signCrossRemoteToken(secret, payload, nowMs, ttlMs = CROSS_REMOTE_TOKEN_TTL_MS) {
232551
- const wire = {
232552
- u: payload.userId,
232553
- s: payload.sessionId,
232554
- src: payload.sourceRemoteServerId,
232555
- exp: nowMs + ttlMs
232556
- };
232557
- const body = Buffer.from(JSON.stringify(wire)).toString("base64url");
232558
- return `${body}.${sign2(secret, body)}`;
232559
- }
232560
- function verifyCrossRemoteToken(secret, token, nowMs) {
232561
- const parts = token.split(".");
232562
- if (parts.length !== 2) return null;
232563
- const [body, providedSig] = parts;
232564
- if (!body || !providedSig) return null;
232565
- const expectedSig = sign2(secret, body);
232566
- const provided = Buffer.from(providedSig);
232567
- const expected = Buffer.from(expectedSig);
232568
- if (provided.length !== expected.length) return null;
232569
- if (!timingSafeEqual2(provided, expected)) return null;
232570
- let wire;
232571
- try {
232572
- wire = JSON.parse(Buffer.from(body, "base64url").toString());
232573
- } catch {
232574
- return null;
232575
- }
232576
- if (typeof wire.u !== "string" || typeof wire.s !== "string" || typeof wire.exp !== "number") return null;
232577
- if (wire.src !== null && typeof wire.src !== "string") return null;
232578
- if (!wire.u || !wire.s) return null;
232579
- if (nowMs >= wire.exp) return null;
232580
- return { userId: wire.u, sessionId: wire.s, sourceRemoteServerId: wire.src };
232581
- }
232582
- async function getCrossRemoteSecret(storage2) {
232583
- return storage2.settings.getOrCreate(
232584
- CROSS_REMOTE_SECRET_SETTING,
232585
- () => randomBytes3(32).toString("hex")
232586
- );
232587
- }
232588
-
232589
- // src/cross-remote-access.ts
232590
- var CROSS_REMOTE_MCP_PATH = "/api/cross-remote-mcp";
232591
- var TOOL_TIERS = {
232592
- remote_read_file: "read",
232593
- remote_list_dir: "read",
232594
- remote_stat_path: "read",
232595
- remote_process_list: "read",
232596
- remote_bash: "exec",
232597
- remote_mcp_open: "exec",
232598
- remote_mcp_list_tools: "exec",
232599
- remote_mcp_call: "exec",
232600
- remote_mcp_ping: "exec",
232601
- remote_mcp_close: "exec"
232602
- };
232603
- var MAX_IN_FLIGHT_PER_SESSION = 4;
232604
- var REMOTE_MCP_CAPABILITIES = [
232605
- "http:POST /api/path/cross-remote/mcp/open",
232606
- "http:POST /api/path/cross-remote/mcp/list-tools",
232607
- "http:POST /api/path/cross-remote/mcp/call",
232608
- "http:POST /api/path/cross-remote/mcp/ping",
232609
- "http:POST /api/path/cross-remote/mcp/close"
232610
- ];
232611
- var supportsRemoteMcpBroker = (server) => REMOTE_MCP_CAPABILITIES.every((capability) => server.worker_capabilities?.includes(capability));
232612
- var tierSatisfies = (granted, required2) => granted === "exec" || granted === "read" && required2 === "read";
232613
- var isOnline = (deps, server) => deps.reverseConnectManager.isConnected(server.id);
232614
- function isSessionUsable(deps, sessionId) {
232615
- if (sessionId.startsWith("remote-")) return deps.remoteSessionMap.has(sessionId);
232616
- return deps.agentSessionManager.getSessionProcessAlive(sessionId);
232617
- }
232618
- async function resolveTarget(deps, payload, targetRemoteId, requiredTier) {
232619
- if (payload.sourceRemoteServerId && payload.sourceRemoteServerId === targetRemoteId) {
232620
- return { ok: false, reason: "not_accessible" };
232621
- }
232622
- const server = await deps.storage.remoteServers.getById(targetRemoteId, payload.userId);
232623
- if (!server) return { ok: false, reason: "not_accessible" };
232624
- if (!tierSatisfies(server.cross_remote_access, requiredTier)) {
232625
- return { ok: false, reason: "not_accessible" };
232626
- }
232627
- if (!isOnline(deps, server)) return { ok: false, reason: "offline" };
232628
- return { ok: true, server };
232629
- }
232630
- async function listAccessibleRemotes(deps, payload) {
232631
- const servers = await deps.storage.remoteServers.getAll(payload.userId);
232632
- return servers.filter((s3) => s3.cross_remote_access !== "off").filter((s3) => s3.id !== payload.sourceRemoteServerId).map((s3) => ({
232633
- id: s3.id,
232634
- name: s3.name,
232635
- access: s3.cross_remote_access,
232636
- online: isOnline(deps, s3),
232637
- mcp_broker_supported: supportsRemoteMcpBroker(s3)
232638
- }));
232639
- }
232640
- var SessionConcurrencyGuard = class {
232641
- constructor(maxInFlight = MAX_IN_FLIGHT_PER_SESSION) {
232642
- this.maxInFlight = maxInFlight;
232643
- }
232644
- maxInFlight;
232645
- inFlight = /* @__PURE__ */ new Map();
232646
- acquire(sessionId) {
232647
- const current = this.inFlight.get(sessionId) ?? 0;
232648
- if (current >= this.maxInFlight) return false;
232649
- this.inFlight.set(sessionId, current + 1);
232650
- return true;
232651
- }
232652
- release(sessionId) {
232653
- const current = this.inFlight.get(sessionId) ?? 0;
232654
- if (current <= 1) this.inFlight.delete(sessionId);
232655
- else this.inFlight.set(sessionId, current - 1);
232656
- }
232657
- };
232658
-
232659
- // src/cross-remote-mcp-config.ts
232660
- async function mintCrossRemoteMcpConfig(deps, args) {
232661
- const baseUrl = process.env.VIBEDECKX_PUBLIC_URL?.trim();
232662
- if (!baseUrl) return void 0;
232663
- const { userId } = args;
232664
- if (!userId) return void 0;
232665
- const servers = await deps.storage.remoteServers.getAll(userId);
232666
- const hasTarget = servers.some(
232667
- (s3) => s3.cross_remote_access !== "off" && s3.id !== args.sourceRemoteServerId
232668
- );
232669
- if (!hasTarget) return void 0;
232670
- const secret = await getCrossRemoteSecret(deps.storage);
232671
- const token = signCrossRemoteToken(
232672
- secret,
232673
- { userId, sessionId: args.sessionId, sourceRemoteServerId: args.sourceRemoteServerId },
232674
- Date.now()
232675
- );
232676
- return { url: `${baseUrl.replace(/\/+$/, "")}${CROSS_REMOTE_MCP_PATH}`, token };
232677
- }
232678
-
232679
232726
  // src/routes/notification-outbox-routes.ts
232680
232727
  var import_fastify_plugin = __toESM(require_plugin2(), 1);
232681
232728
  var MAX_SESSIONS_PER_REQUEST = 100;
@@ -239708,6 +239755,7 @@ function selfReportSection(report) {
239708
239755
  var VERDICT_INSTRUCTIONS = [
239709
239756
  "\nThe bar for blocking: a real defect that is worth fixing \u2014 wrong behavior, a case a user or caller will actually hit, a security or data-loss risk, or a missing test for logic that matters. Report those plainly; do not soften a real problem because the fix is inconvenient.",
239710
239757
  "Not blocking: over-engineering \u2014 speculative hardening, defenses against inputs this code cannot receive, abstractions or configurability for cases nobody has asked for, or a rewrite in your preferred style. When the fix would add more complexity than the problem it prevents is worth, it is a non-blocking note at most.",
239758
+ "Both halves matter equally: solve real problems, and do not over-engineer.",
239711
239759
  "\nEnd your final message with:",
239712
239760
  "1. Verdict \u2014 exactly one of: ship / needs-changes / cannot-verify. Use cannot-verify when you could not gather enough evidence to judge, rather than guessing.",
239713
239761
  "2. Blocking findings \u2014 what must change before shipping, each specific and actionable (say explicitly when there are none).",
@@ -241998,7 +242046,11 @@ var TITLE_BY_KIND = {
241998
242046
  review_ready: "Review feedback is ready",
241999
242047
  session_result_ready: "Session result is ready",
242000
242048
  session_failed: "Session failed",
242001
- workflow_failed: "Workflow needs attention"
242049
+ workflow_failed: "Workflow needs attention",
242050
+ // "Stop, then send" — NOT "restart": restartSession wipes the conversation
242051
+ // history, while stop → dormant → next message respawns with a fresh token
242052
+ // and keeps everything.
242053
+ cross_remote_token_expired: "Cross-remote access expired \u2014 stop the session, then send a message to renew"
242002
242054
  };
242003
242055
  var PLACEHOLDER_TITLES = /* @__PURE__ */ new Set(["New Session", "Generating title\u2026", "Generating title..."]);
242004
242056
  var LOCAL_CURSOR_KEY = "notification_local_cursor";
@@ -247835,7 +247887,7 @@ var routes11 = async (fastify2) => {
247835
247887
  const result = await fastify2.agentSessionManager.branchSession(
247836
247888
  sourceSessionId,
247837
247889
  opts.agentType,
247838
- { sessionId: opts.sessionId, crossRemoteMcp: opts.crossRemoteMcp, upToEntryIndex: opts.upToEntryIndex }
247890
+ { sessionId: opts.sessionId, crossRemoteMcp: opts.crossRemoteMcp, upToEntryIndex: opts.upToEntryIndex, userId }
247839
247891
  );
247840
247892
  if (!result.ok) {
247841
247893
  if (result.reason === "invalid-cutoff") {
@@ -248546,7 +248598,7 @@ var routes11 = async (fastify2) => {
248546
248598
  agentType || "claude-code",
248547
248599
  false,
248548
248600
  force === true,
248549
- { sessionId: preSessionId, crossRemoteMcp, model }
248601
+ { sessionId: preSessionId, crossRemoteMcp, model, userId: userId ?? void 0 }
248550
248602
  );
248551
248603
  const session = fastify2.agentSessionManager.getSession(sessionId);
248552
248604
  return reply.code(200).send({
@@ -248830,12 +248882,27 @@ var routes11 = async (fastify2) => {
248830
248882
  errorCode: "notification_baseline_failed"
248831
248883
  });
248832
248884
  }
248885
+ const freshCrossRemoteMcp = typeof authResult === "string" ? await mintCrossRemoteMcpConfig(
248886
+ { storage: fastify2.storage },
248887
+ {
248888
+ userId: authResult,
248889
+ sessionId: req.params.sessionId,
248890
+ sourceRemoteServerId: remoteInfo.remoteServerId
248891
+ }
248892
+ ).catch((err) => {
248893
+ console.error(`[API] cross-remote token refresh mint failed for ${req.params.sessionId}:`, err);
248894
+ return void 0;
248895
+ }) : void 0;
248833
248896
  const activityAt = Date.now();
248834
248897
  const result = await proxyAuto(
248835
248898
  remoteInfo.remoteServerId,
248836
248899
  "POST",
248837
248900
  `/api/agent-sessions/${remoteInfo.remoteSessionId}/message`,
248838
- { content, ...idempotencyKey ? { idempotencyKey } : {} }
248901
+ {
248902
+ content,
248903
+ ...idempotencyKey ? { idempotencyKey } : {},
248904
+ ...freshCrossRemoteMcp ? { crossRemoteMcp: freshCrossRemoteMcp } : {}
248905
+ }
248839
248906
  );
248840
248907
  if (!result.ok) {
248841
248908
  const status = proxyStatus(result);
@@ -248894,6 +248961,13 @@ var routes11 = async (fastify2) => {
248894
248961
  if (!storedSession || !storedProjection || !await fastify2.storage.projects.getById(storedProjection.projectId, authResult)) {
248895
248962
  return reply.code(404).send({ error: "Session not found or not running" });
248896
248963
  }
248964
+ const incomingCrossRemoteMcp = req.body.crossRemoteMcp;
248965
+ if (incomingCrossRemoteMcp && typeof incomingCrossRemoteMcp.url === "string" && typeof incomingCrossRemoteMcp.token === "string") {
248966
+ fastify2.agentSessionManager.updateCrossRemoteMcp(req.params.sessionId, {
248967
+ url: incomingCrossRemoteMcp.url,
248968
+ token: incomingCrossRemoteMcp.token
248969
+ });
248970
+ }
248897
248971
  const session = fastify2.agentSessionManager.getSession(req.params.sessionId);
248898
248972
  let projectPathForWake;
248899
248973
  if (session?.dormant) {
@@ -250876,10 +250950,14 @@ async function routes20(fastify2) {
250876
250950
  result.status === 0 ? { error: `Remote proxy failed: ${result.errorCode || "unknown"}` } : result.data
250877
250951
  );
250878
250952
  const resolveRemoteRun = async (runId, userId) => {
250879
- const info = remoteRunMap.get(runId);
250953
+ const tracked = remoteRunMap.get(runId);
250954
+ const info = tracked ?? parseRemoteRunId(runId);
250880
250955
  if (!info) return null;
250881
250956
  const project = await fastify2.storage.projects.getById(info.projectId, userId);
250882
250957
  if (!project) return null;
250958
+ if (!tracked && !await fastify2.storage.projectRemotes.getByProjectAndServer(info.projectId, info.remoteServerId)) {
250959
+ return null;
250960
+ }
250883
250961
  return info;
250884
250962
  };
250885
250963
  const distillIntentBrief = async (userId, sourceSessionId) => {
@@ -251272,19 +251350,27 @@ async function routes20(fastify2) {
251272
251350
  };
251273
251351
  const result = await proxyAuto(info, "GET", `/api/path/workflow-runs?${q}`);
251274
251352
  if (!result.ok) return sendProxyFailure(reply, result);
251275
- const bareRuns = result.data.runs ?? [];
251353
+ const data = result.data;
251354
+ const bareRuns = data.runs ?? [];
251276
251355
  const runs2 = bareRuns.map((r) => {
251277
251356
  const mapped = mapRemoteRun(r, info.remoteServerId, projectId);
251278
251357
  trackRemoteRun(mapped, { ...info, bareRunId: r.id, projectId });
251279
251358
  return mapped;
251280
251359
  });
251281
251360
  logRead(runs2.length, `remote:${info.remoteServerId}`);
251282
- return reply.send({ runs: runs2 });
251361
+ const prefix = `remote-${info.remoteServerId}-${projectId}-`;
251362
+ return reply.send({
251363
+ runs: runs2,
251364
+ ...data.reviewedSessionIds ? { reviewedSessionIds: data.reviewedSessionIds.map((id) => prefix + id) } : {}
251365
+ });
251283
251366
  }
251284
251367
  }
251285
- const runs = await fastify2.storage.workflowRuns.getActive(projectId, branch ?? null);
251368
+ const [runs, reviewedSessionIds] = await Promise.all([
251369
+ fastify2.storage.workflowRuns.getActive(projectId, branch ?? null),
251370
+ fastify2.storage.workflowRuns.listReviewedSourceSessions(projectId, branch ?? null)
251371
+ ]);
251286
251372
  logRead(runs.length, "local");
251287
- return reply.send({ runs });
251373
+ return reply.send({ runs, reviewedSessionIds });
251288
251374
  }
251289
251375
  );
251290
251376
  fastify2.get("/api/workflow-runs/:id", async (req, reply) => {
@@ -251536,9 +251622,12 @@ async function routes20(fastify2) {
251536
251622
  const { path: projectPath, branch } = req.query;
251537
251623
  if (!projectPath) return reply.code(400).send({ error: "path is required" });
251538
251624
  const project = await fastify2.storage.projects.getByPath(projectPath) ?? await fastify2.storage.projects.getById(`path:${projectPath}`);
251539
- if (!project) return reply.send({ runs: [] });
251540
- const runs = await fastify2.storage.workflowRuns.getActive(project.id, branch || null);
251541
- return reply.send({ runs });
251625
+ if (!project) return reply.send({ runs: [], reviewedSessionIds: [] });
251626
+ const [runs, reviewedSessionIds] = await Promise.all([
251627
+ fastify2.storage.workflowRuns.getActive(project.id, branch || null),
251628
+ fastify2.storage.workflowRuns.listReviewedSourceSessions(project.id, branch || null)
251629
+ ]);
251630
+ return reply.send({ runs, reviewedSessionIds });
251542
251631
  });
251543
251632
  }
251544
251633
  var workflow_run_routes_default = (0, import_fastify_plugin21.default)(routes20, { name: "workflow-run-routes" });
@@ -259609,14 +259698,68 @@ var routes30 = async (fastify2) => {
259609
259698
  if (!cachedSecret) cachedSecret = getCrossRemoteSecret(fastify2.storage);
259610
259699
  return cachedSecret;
259611
259700
  };
259701
+ const expiredNotifyAt = /* @__PURE__ */ new Map();
259702
+ const EXPIRED_NOTIFY_MIN_INTERVAL_MS = 6e4;
259703
+ const notifyTokenExpired = async (payload, exp) => {
259704
+ const last = expiredNotifyAt.get(payload.sessionId);
259705
+ const now3 = Date.now();
259706
+ if (last !== void 0 && now3 - last < EXPIRED_NOTIFY_MIN_INTERVAL_MS) return;
259707
+ expiredNotifyAt.set(payload.sessionId, now3);
259708
+ let projectId;
259709
+ let branch = null;
259710
+ const runtime2 = fastify2.agentSessionManager.getSession(payload.sessionId);
259711
+ if (runtime2) {
259712
+ projectId = runtime2.projectId;
259713
+ branch = runtime2.branch;
259714
+ } else if (payload.sessionId.startsWith("remote-")) {
259715
+ const mapping = await fastify2.storage.remoteSessionMappings.getByLocal(payload.sessionId);
259716
+ if (mapping) {
259717
+ projectId = mapping.project_id;
259718
+ branch = mapping.branch ?? null;
259719
+ }
259720
+ } else {
259721
+ const row = await fastify2.storage.agentSessions.getById(payload.sessionId);
259722
+ if (row) {
259723
+ projectId = row.project_id;
259724
+ branch = row.branch || null;
259725
+ }
259726
+ }
259727
+ if (!projectId) return;
259728
+ const project = await fastify2.storage.projects.getById(projectId);
259729
+ const session = await fastify2.storage.agentSessions.getById(payload.sessionId);
259730
+ const notification = {
259731
+ // Deterministic per token instance: retries and multiple expired calls
259732
+ // from the same process collapse onto one inbox row.
259733
+ id: `cross-remote-expired:${payload.sessionId}:${exp}`,
259734
+ user_id: payload.userId,
259735
+ kind: "cross_remote_token_expired",
259736
+ project_id: projectId,
259737
+ branch,
259738
+ session_id: payload.sessionId,
259739
+ workflow_run_id: null,
259740
+ title: notificationTitle("cross_remote_token_expired"),
259741
+ body: notificationBody({ sessionTitle: session?.title, branch, projectName: project?.name }),
259742
+ created_at: now3,
259743
+ read_at: null
259744
+ };
259745
+ if (await fastify2.storage.notifications.insert(notification)) {
259746
+ fastify2.eventBus.emit({ type: "notification:created", projectId, notification });
259747
+ }
259748
+ };
259612
259749
  const authenticate = async (request) => {
259613
259750
  const header = request.headers.authorization;
259614
259751
  if (!header?.startsWith("Bearer ")) return null;
259615
259752
  const secret = await getSecret();
259616
- const payload = verifyCrossRemoteToken(secret, header.slice("Bearer ".length), Date.now());
259617
- if (!payload) return null;
259618
- if (!isSessionUsable(fastify2, payload.sessionId)) return null;
259619
- return payload;
259753
+ const verified = verifyCrossRemoteTokenDetailed(secret, header.slice("Bearer ".length), Date.now());
259754
+ if (verified.status === "expired") {
259755
+ void notifyTokenExpired(verified.payload, verified.exp).catch(
259756
+ (err) => console.error("[CrossRemoteMCP] expired-token notification failed:", err)
259757
+ );
259758
+ return null;
259759
+ }
259760
+ if (verified.status !== "ok") return null;
259761
+ if (!isSessionUsable(fastify2, verified.payload.sessionId)) return null;
259762
+ return verified.payload;
259620
259763
  };
259621
259764
  const audit = async (payload, targetRemoteId, toolName, summary, status, exitCode, startedAt) => {
259622
259765
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.3.30",
3
+ "version": "0.3.32",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"