@vibedeckx/linux-x64 0.3.31 → 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 +299 -177
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -207864,6 +207864,176 @@ function getAllProviders() {
207864
207864
  return Array.from(providers.values());
207865
207865
  }
207866
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
+
207867
208037
  // src/conversation-patch.ts
207868
208038
  var ConversationPatch = {
207869
208039
  /**
@@ -230232,6 +230402,7 @@ var AgentSessionManager = class {
230232
230402
  skipDb,
230233
230403
  permissionMode,
230234
230404
  crossRemoteMcp: opts.crossRemoteMcp,
230405
+ userId: opts.userId && opts.userId !== "local" ? opts.userId : void 0,
230235
230406
  agentType,
230236
230407
  model,
230237
230408
  completion: new TurnCompletionLedger(this.parkTimeoutMs),
@@ -230286,6 +230457,18 @@ var AgentSessionManager = class {
230286
230457
  await this.restartSession(session.id, projectPath);
230287
230458
  return session.id;
230288
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
+ }
230289
230472
  /**
230290
230473
  * Kill an agent process and its entire process tree.
230291
230474
  * Uses negative PID to signal the process group (requires detached: true at spawn).
@@ -230332,6 +230515,16 @@ var AgentSessionManager = class {
230332
230515
  console.error(`[AgentSession] Failed to mint session tools MCP config for ${session.id}:`, err);
230333
230516
  return void 0;
230334
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
+ }
230335
230528
  const config2 = provider.buildSpawnConfig(
230336
230529
  cwd,
230337
230530
  session.permissionMode,
@@ -231102,6 +231295,7 @@ var AgentSessionManager = class {
231102
231295
  async sendUserMessage(sessionId, content, projectPath, userId = "local", opts) {
231103
231296
  const session = this.sessions.get(sessionId);
231104
231297
  if (!session) return false;
231298
+ if (userId && userId !== "local" && !session.userId) session.userId = userId;
231105
231299
  const disposition = this.resolveOutgoingDisposition(sessionId, opts);
231106
231300
  if (session.dormant) {
231107
231301
  if (!projectPath) {
@@ -232223,6 +232417,7 @@ var AgentSessionManager = class {
232223
232417
  turnOpenSince: null,
232224
232418
  turnDisposition: null,
232225
232419
  crossRemoteMcp: opts.crossRemoteMcp,
232420
+ userId: opts.userId && opts.userId !== "local" ? opts.userId : void 0,
232226
232421
  branchedFromSessionId: sourceSessionId,
232227
232422
  branchedFromEntryIndex
232228
232423
  };
@@ -232309,7 +232504,7 @@ import { randomUUID as randomUUID3 } from "crypto";
232309
232504
 
232310
232505
  // src/trace-context.ts
232311
232506
  import { AsyncLocalStorage } from "node:async_hooks";
232312
- import { randomBytes as randomBytes2 } from "node:crypto";
232507
+ import { randomBytes as randomBytes3 } from "node:crypto";
232313
232508
  var VERSION8 = "00";
232314
232509
  var INVALID_VERSION = "ff";
232315
232510
  var ZERO_TRACE_ID = "0".repeat(32);
@@ -232334,10 +232529,10 @@ function formatTraceparent(ctx) {
232334
232529
  return `${VERSION8}-${ctx.traceId}-${ctx.spanId}-${ctx.sampled ? "01" : "00"}`;
232335
232530
  }
232336
232531
  function newTraceId() {
232337
- return randomBytes2(16).toString("hex");
232532
+ return randomBytes3(16).toString("hex");
232338
232533
  }
232339
232534
  function newSpanId() {
232340
- return randomBytes2(8).toString("hex");
232535
+ return randomBytes3(8).toString("hex");
232341
232536
  }
232342
232537
  function newTraceContext(incoming) {
232343
232538
  const parsed = parseTraceparent(incoming);
@@ -232503,6 +232698,13 @@ function mapRemoteRun(run2, remoteServerId, projectId) {
232503
232698
  reviewer_session_id: run2.reviewer_session_id ? `${prefix}${run2.reviewer_session_id}` : null
232504
232699
  };
232505
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
+ }
232506
232708
  function mapRemoteReviewerCandidate(candidate, remoteServerId, projectId) {
232507
232709
  if (!candidate?.sessionId) return candidate;
232508
232710
  return {
@@ -232521,171 +232723,6 @@ function runUpdatedFrameForSubscribers(evt) {
232521
232723
  return JSON.stringify({ workflowRunUpdated: evt.run });
232522
232724
  }
232523
232725
 
232524
- // src/utils/cross-remote-token.ts
232525
- import { createHmac as createHmac2, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
232526
- var CROSS_REMOTE_SECRET_SETTING = "cross_remote_token_secret";
232527
- var CROSS_REMOTE_TOKEN_TTL_MS = 864e5;
232528
- var sign2 = (secret, body) => createHmac2("sha256", secret).update(body).digest("base64url");
232529
- function signRemoteMcpHandle(secret, payload, nowMs, ttlMs = CROSS_REMOTE_TOKEN_TTL_MS) {
232530
- const wire = {
232531
- u: payload.userId,
232532
- s: payload.sessionId,
232533
- r: payload.remoteId,
232534
- h: payload.workerHandle,
232535
- n: payload.serverLabel,
232536
- exp: nowMs + ttlMs
232537
- };
232538
- const body = Buffer.from(JSON.stringify(wire)).toString("base64url");
232539
- return `mcp.${body}.${sign2(secret, `mcp:${body}`)}`;
232540
- }
232541
- function verifyRemoteMcpHandle(secret, handle, nowMs) {
232542
- const parts = handle.split(".");
232543
- if (parts.length !== 3) return null;
232544
- const [prefix, body, providedSig] = parts;
232545
- if (prefix !== "mcp" || !body || !providedSig) return null;
232546
- const expectedSig = sign2(secret, `mcp:${body}`);
232547
- const provided = Buffer.from(providedSig);
232548
- const expected = Buffer.from(expectedSig);
232549
- if (provided.length !== expected.length || !timingSafeEqual2(provided, expected)) return null;
232550
- let wire;
232551
- try {
232552
- wire = JSON.parse(Buffer.from(body, "base64url").toString());
232553
- } catch {
232554
- return null;
232555
- }
232556
- if (![wire.u, wire.s, wire.r, wire.h, wire.n].every((v2) => typeof v2 === "string" && v2.length > 0)) return null;
232557
- if (typeof wire.exp !== "number" || nowMs >= wire.exp) return null;
232558
- return { userId: wire.u, sessionId: wire.s, remoteId: wire.r, workerHandle: wire.h, serverLabel: wire.n };
232559
- }
232560
- function signCrossRemoteToken(secret, payload, nowMs, ttlMs = CROSS_REMOTE_TOKEN_TTL_MS) {
232561
- const wire = {
232562
- u: payload.userId,
232563
- s: payload.sessionId,
232564
- src: payload.sourceRemoteServerId,
232565
- exp: nowMs + ttlMs
232566
- };
232567
- const body = Buffer.from(JSON.stringify(wire)).toString("base64url");
232568
- return `${body}.${sign2(secret, body)}`;
232569
- }
232570
- function verifyCrossRemoteToken(secret, token, nowMs) {
232571
- const parts = token.split(".");
232572
- if (parts.length !== 2) return null;
232573
- const [body, providedSig] = parts;
232574
- if (!body || !providedSig) return null;
232575
- const expectedSig = sign2(secret, body);
232576
- const provided = Buffer.from(providedSig);
232577
- const expected = Buffer.from(expectedSig);
232578
- if (provided.length !== expected.length) return null;
232579
- if (!timingSafeEqual2(provided, expected)) return null;
232580
- let wire;
232581
- try {
232582
- wire = JSON.parse(Buffer.from(body, "base64url").toString());
232583
- } catch {
232584
- return null;
232585
- }
232586
- if (typeof wire.u !== "string" || typeof wire.s !== "string" || typeof wire.exp !== "number") return null;
232587
- if (wire.src !== null && typeof wire.src !== "string") return null;
232588
- if (!wire.u || !wire.s) return null;
232589
- if (nowMs >= wire.exp) return null;
232590
- return { userId: wire.u, sessionId: wire.s, sourceRemoteServerId: wire.src };
232591
- }
232592
- async function getCrossRemoteSecret(storage2) {
232593
- return storage2.settings.getOrCreate(
232594
- CROSS_REMOTE_SECRET_SETTING,
232595
- () => randomBytes3(32).toString("hex")
232596
- );
232597
- }
232598
-
232599
- // src/cross-remote-access.ts
232600
- var CROSS_REMOTE_MCP_PATH = "/api/cross-remote-mcp";
232601
- var TOOL_TIERS = {
232602
- remote_read_file: "read",
232603
- remote_list_dir: "read",
232604
- remote_stat_path: "read",
232605
- remote_process_list: "read",
232606
- remote_bash: "exec",
232607
- remote_mcp_open: "exec",
232608
- remote_mcp_list_tools: "exec",
232609
- remote_mcp_call: "exec",
232610
- remote_mcp_ping: "exec",
232611
- remote_mcp_close: "exec"
232612
- };
232613
- var MAX_IN_FLIGHT_PER_SESSION = 4;
232614
- var REMOTE_MCP_CAPABILITIES = [
232615
- "http:POST /api/path/cross-remote/mcp/open",
232616
- "http:POST /api/path/cross-remote/mcp/list-tools",
232617
- "http:POST /api/path/cross-remote/mcp/call",
232618
- "http:POST /api/path/cross-remote/mcp/ping",
232619
- "http:POST /api/path/cross-remote/mcp/close"
232620
- ];
232621
- var supportsRemoteMcpBroker = (server) => REMOTE_MCP_CAPABILITIES.every((capability) => server.worker_capabilities?.includes(capability));
232622
- var tierSatisfies = (granted, required2) => granted === "exec" || granted === "read" && required2 === "read";
232623
- var isOnline = (deps, server) => deps.reverseConnectManager.isConnected(server.id);
232624
- function isSessionUsable(deps, sessionId) {
232625
- if (sessionId.startsWith("remote-")) return deps.remoteSessionMap.has(sessionId);
232626
- return deps.agentSessionManager.getSessionProcessAlive(sessionId);
232627
- }
232628
- async function resolveTarget(deps, payload, targetRemoteId, requiredTier) {
232629
- if (payload.sourceRemoteServerId && payload.sourceRemoteServerId === targetRemoteId) {
232630
- return { ok: false, reason: "not_accessible" };
232631
- }
232632
- const server = await deps.storage.remoteServers.getById(targetRemoteId, payload.userId);
232633
- if (!server) return { ok: false, reason: "not_accessible" };
232634
- if (!tierSatisfies(server.cross_remote_access, requiredTier)) {
232635
- return { ok: false, reason: "not_accessible" };
232636
- }
232637
- if (!isOnline(deps, server)) return { ok: false, reason: "offline" };
232638
- return { ok: true, server };
232639
- }
232640
- async function listAccessibleRemotes(deps, payload) {
232641
- const servers = await deps.storage.remoteServers.getAll(payload.userId);
232642
- return servers.filter((s3) => s3.cross_remote_access !== "off").filter((s3) => s3.id !== payload.sourceRemoteServerId).map((s3) => ({
232643
- id: s3.id,
232644
- name: s3.name,
232645
- access: s3.cross_remote_access,
232646
- online: isOnline(deps, s3),
232647
- mcp_broker_supported: supportsRemoteMcpBroker(s3)
232648
- }));
232649
- }
232650
- var SessionConcurrencyGuard = class {
232651
- constructor(maxInFlight = MAX_IN_FLIGHT_PER_SESSION) {
232652
- this.maxInFlight = maxInFlight;
232653
- }
232654
- maxInFlight;
232655
- inFlight = /* @__PURE__ */ new Map();
232656
- acquire(sessionId) {
232657
- const current = this.inFlight.get(sessionId) ?? 0;
232658
- if (current >= this.maxInFlight) return false;
232659
- this.inFlight.set(sessionId, current + 1);
232660
- return true;
232661
- }
232662
- release(sessionId) {
232663
- const current = this.inFlight.get(sessionId) ?? 0;
232664
- if (current <= 1) this.inFlight.delete(sessionId);
232665
- else this.inFlight.set(sessionId, current - 1);
232666
- }
232667
- };
232668
-
232669
- // src/cross-remote-mcp-config.ts
232670
- async function mintCrossRemoteMcpConfig(deps, args) {
232671
- const baseUrl = process.env.VIBEDECKX_PUBLIC_URL?.trim();
232672
- if (!baseUrl) return void 0;
232673
- const { userId } = args;
232674
- if (!userId) return void 0;
232675
- const servers = await deps.storage.remoteServers.getAll(userId);
232676
- const hasTarget = servers.some(
232677
- (s3) => s3.cross_remote_access !== "off" && s3.id !== args.sourceRemoteServerId
232678
- );
232679
- if (!hasTarget) return void 0;
232680
- const secret = await getCrossRemoteSecret(deps.storage);
232681
- const token = signCrossRemoteToken(
232682
- secret,
232683
- { userId, sessionId: args.sessionId, sourceRemoteServerId: args.sourceRemoteServerId },
232684
- Date.now()
232685
- );
232686
- return { url: `${baseUrl.replace(/\/+$/, "")}${CROSS_REMOTE_MCP_PATH}`, token };
232687
- }
232688
-
232689
232726
  // src/routes/notification-outbox-routes.ts
232690
232727
  var import_fastify_plugin = __toESM(require_plugin2(), 1);
232691
232728
  var MAX_SESSIONS_PER_REQUEST = 100;
@@ -239718,6 +239755,7 @@ function selfReportSection(report) {
239718
239755
  var VERDICT_INSTRUCTIONS = [
239719
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.",
239720
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.",
239721
239759
  "\nEnd your final message with:",
239722
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.",
239723
239761
  "2. Blocking findings \u2014 what must change before shipping, each specific and actionable (say explicitly when there are none).",
@@ -242008,7 +242046,11 @@ var TITLE_BY_KIND = {
242008
242046
  review_ready: "Review feedback is ready",
242009
242047
  session_result_ready: "Session result is ready",
242010
242048
  session_failed: "Session failed",
242011
- 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"
242012
242054
  };
242013
242055
  var PLACEHOLDER_TITLES = /* @__PURE__ */ new Set(["New Session", "Generating title\u2026", "Generating title..."]);
242014
242056
  var LOCAL_CURSOR_KEY = "notification_local_cursor";
@@ -247845,7 +247887,7 @@ var routes11 = async (fastify2) => {
247845
247887
  const result = await fastify2.agentSessionManager.branchSession(
247846
247888
  sourceSessionId,
247847
247889
  opts.agentType,
247848
- { sessionId: opts.sessionId, crossRemoteMcp: opts.crossRemoteMcp, upToEntryIndex: opts.upToEntryIndex }
247890
+ { sessionId: opts.sessionId, crossRemoteMcp: opts.crossRemoteMcp, upToEntryIndex: opts.upToEntryIndex, userId }
247849
247891
  );
247850
247892
  if (!result.ok) {
247851
247893
  if (result.reason === "invalid-cutoff") {
@@ -248556,7 +248598,7 @@ var routes11 = async (fastify2) => {
248556
248598
  agentType || "claude-code",
248557
248599
  false,
248558
248600
  force === true,
248559
- { sessionId: preSessionId, crossRemoteMcp, model }
248601
+ { sessionId: preSessionId, crossRemoteMcp, model, userId: userId ?? void 0 }
248560
248602
  );
248561
248603
  const session = fastify2.agentSessionManager.getSession(sessionId);
248562
248604
  return reply.code(200).send({
@@ -248840,12 +248882,27 @@ var routes11 = async (fastify2) => {
248840
248882
  errorCode: "notification_baseline_failed"
248841
248883
  });
248842
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;
248843
248896
  const activityAt = Date.now();
248844
248897
  const result = await proxyAuto(
248845
248898
  remoteInfo.remoteServerId,
248846
248899
  "POST",
248847
248900
  `/api/agent-sessions/${remoteInfo.remoteSessionId}/message`,
248848
- { content, ...idempotencyKey ? { idempotencyKey } : {} }
248901
+ {
248902
+ content,
248903
+ ...idempotencyKey ? { idempotencyKey } : {},
248904
+ ...freshCrossRemoteMcp ? { crossRemoteMcp: freshCrossRemoteMcp } : {}
248905
+ }
248849
248906
  );
248850
248907
  if (!result.ok) {
248851
248908
  const status = proxyStatus(result);
@@ -248904,6 +248961,13 @@ var routes11 = async (fastify2) => {
248904
248961
  if (!storedSession || !storedProjection || !await fastify2.storage.projects.getById(storedProjection.projectId, authResult)) {
248905
248962
  return reply.code(404).send({ error: "Session not found or not running" });
248906
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
+ }
248907
248971
  const session = fastify2.agentSessionManager.getSession(req.params.sessionId);
248908
248972
  let projectPathForWake;
248909
248973
  if (session?.dormant) {
@@ -250886,10 +250950,14 @@ async function routes20(fastify2) {
250886
250950
  result.status === 0 ? { error: `Remote proxy failed: ${result.errorCode || "unknown"}` } : result.data
250887
250951
  );
250888
250952
  const resolveRemoteRun = async (runId, userId) => {
250889
- const info = remoteRunMap.get(runId);
250953
+ const tracked = remoteRunMap.get(runId);
250954
+ const info = tracked ?? parseRemoteRunId(runId);
250890
250955
  if (!info) return null;
250891
250956
  const project = await fastify2.storage.projects.getById(info.projectId, userId);
250892
250957
  if (!project) return null;
250958
+ if (!tracked && !await fastify2.storage.projectRemotes.getByProjectAndServer(info.projectId, info.remoteServerId)) {
250959
+ return null;
250960
+ }
250893
250961
  return info;
250894
250962
  };
250895
250963
  const distillIntentBrief = async (userId, sourceSessionId) => {
@@ -259630,14 +259698,68 @@ var routes30 = async (fastify2) => {
259630
259698
  if (!cachedSecret) cachedSecret = getCrossRemoteSecret(fastify2.storage);
259631
259699
  return cachedSecret;
259632
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
+ };
259633
259749
  const authenticate = async (request) => {
259634
259750
  const header = request.headers.authorization;
259635
259751
  if (!header?.startsWith("Bearer ")) return null;
259636
259752
  const secret = await getSecret();
259637
- const payload = verifyCrossRemoteToken(secret, header.slice("Bearer ".length), Date.now());
259638
- if (!payload) return null;
259639
- if (!isSessionUsable(fastify2, payload.sessionId)) return null;
259640
- 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;
259641
259763
  };
259642
259764
  const audit = async (payload, targetRemoteId, toolName, summary, status, exitCode, startedAt) => {
259643
259765
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedeckx/linux-x64",
3
- "version": "0.3.31",
3
+ "version": "0.3.32",
4
4
  "description": "Vibedeckx platform binaries for Linux x64",
5
5
  "os": [
6
6
  "linux"