@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.
- package/dist/bin.js +299 -177
- 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
|
|
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
|
|
232532
|
+
return randomBytes3(16).toString("hex");
|
|
232338
232533
|
}
|
|
232339
232534
|
function newSpanId() {
|
|
232340
|
-
return
|
|
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
|
-
{
|
|
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
|
|
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
|
|
259638
|
-
if (
|
|
259639
|
-
|
|
259640
|
-
|
|
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 {
|