svamp-cli 0.2.47 → 0.2.49
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/{agentCommands-BuGwfYhd.mjs → agentCommands-CpR_jpWk.mjs} +2 -2
- package/dist/cli.mjs +68 -33
- package/dist/{commands-TyAIFJx-.mjs → commands-BswZUToD.mjs} +2 -2
- package/dist/{commands-JWrmpGcs.mjs → commands-CTQAVadm.mjs} +46 -32
- package/dist/{commands-BJR_98XX.mjs → commands-DmxhoO5X.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/package-DnTJGm-n.mjs +63 -0
- package/dist/{run-6umeTX-K.mjs → run-V2qpcN7f.mjs} +378 -8
- package/dist/{run-DR7E3IZL.mjs → run-mXTPYgtc.mjs} +1 -1
- package/dist/{serveCommands-FUE8m232.mjs → serveCommands-Cj-P-FeY.mjs} +5 -5
- package/dist/{serveManager-RvRL-weX.mjs → serveManager-BhWfboEx.mjs} +17 -230
- package/package.json +2 -2
- package/dist/package-CNFS7wvh.mjs +0 -63
|
@@ -15,7 +15,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
15
15
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
16
16
|
import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
17
17
|
import { z } from 'zod';
|
|
18
|
-
import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile,
|
|
18
|
+
import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile, readdir, stat, readFile as readFile$1 } from 'node:fs/promises';
|
|
19
19
|
import { promisify } from 'node:util';
|
|
20
20
|
|
|
21
21
|
let connectToServerFn = null;
|
|
@@ -71,6 +71,14 @@ const ROLE_HIERARCHY = {
|
|
|
71
71
|
interact: 1,
|
|
72
72
|
admin: 2
|
|
73
73
|
};
|
|
74
|
+
function normalizeAllowedUser(input, addedBy) {
|
|
75
|
+
return {
|
|
76
|
+
email: input.email.trim(),
|
|
77
|
+
role: "admin",
|
|
78
|
+
addedAt: typeof input.addedAt === "number" ? input.addedAt : Date.now(),
|
|
79
|
+
addedBy: input.addedBy ?? addedBy
|
|
80
|
+
};
|
|
81
|
+
}
|
|
74
82
|
const ISOLATION_PREFERENCE = ["nono", "docker", "podman"];
|
|
75
83
|
|
|
76
84
|
function resolveRoleLevel(sharing, userEmail) {
|
|
@@ -80,7 +88,7 @@ function resolveRoleLevel(sharing, userEmail) {
|
|
|
80
88
|
(u) => u.email.toLowerCase() === userEmail.toLowerCase()
|
|
81
89
|
);
|
|
82
90
|
if (sharedUser) {
|
|
83
|
-
level = ROLE_HIERARCHY
|
|
91
|
+
level = ROLE_HIERARCHY.admin;
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
if (sharing.publicAccess) {
|
|
@@ -737,6 +745,14 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
737
745
|
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
738
746
|
newSharing = { ...newSharing, owner: context.user.email };
|
|
739
747
|
}
|
|
748
|
+
const ownerEmail = newSharing.owner || context?.user?.email || "";
|
|
749
|
+
newSharing = {
|
|
750
|
+
...newSharing,
|
|
751
|
+
allowedUsers: (newSharing.allowedUsers || []).map(
|
|
752
|
+
(u) => normalizeAllowedUser(u, ownerEmail)
|
|
753
|
+
),
|
|
754
|
+
publicAccess: null
|
|
755
|
+
};
|
|
740
756
|
const oldSharing = currentMetadata.sharing;
|
|
741
757
|
currentMetadata = { ...currentMetadata, sharing: newSharing };
|
|
742
758
|
metadataVersion++;
|
|
@@ -1748,6 +1764,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
1748
1764
|
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
1749
1765
|
newSharing = { ...newSharing, owner: context.user.email };
|
|
1750
1766
|
}
|
|
1767
|
+
const ownerEmail = newSharing.owner || context?.user?.email || "";
|
|
1768
|
+
newSharing = {
|
|
1769
|
+
...newSharing,
|
|
1770
|
+
allowedUsers: (newSharing.allowedUsers || []).map(
|
|
1771
|
+
(u) => normalizeAllowedUser(u, ownerEmail)
|
|
1772
|
+
)
|
|
1773
|
+
};
|
|
1751
1774
|
metadata = { ...metadata, sharing: newSharing };
|
|
1752
1775
|
metadataVersion++;
|
|
1753
1776
|
notifyListeners({
|
|
@@ -2576,7 +2599,8 @@ class SharingNotificationSync {
|
|
|
2576
2599
|
userEmail: params.ownerEmail
|
|
2577
2600
|
},
|
|
2578
2601
|
title: `${typeLabel} shared with you`,
|
|
2579
|
-
|
|
2602
|
+
// Per-user tiers were retired; explicit users always have full access.
|
|
2603
|
+
body: `${params.ownerEmail} shared "${params.label || "Untitled"}" with you (full access)`,
|
|
2580
2604
|
level: "info",
|
|
2581
2605
|
action: shareType === "session" ? {
|
|
2582
2606
|
type: "add-session-bookmark",
|
|
@@ -4939,6 +4963,28 @@ function sanitizeEnvForSharing(env) {
|
|
|
4939
4963
|
}
|
|
4940
4964
|
return sanitized;
|
|
4941
4965
|
}
|
|
4966
|
+
async function sweepOrphanedStagedHomes(activeSessionIds) {
|
|
4967
|
+
const removed = [];
|
|
4968
|
+
if (!existsSync(STAGED_HOMES_DIR)) return { removed };
|
|
4969
|
+
const active = new Set(activeSessionIds);
|
|
4970
|
+
let entries;
|
|
4971
|
+
try {
|
|
4972
|
+
entries = await readdir(STAGED_HOMES_DIR, { withFileTypes: true });
|
|
4973
|
+
} catch {
|
|
4974
|
+
return { removed };
|
|
4975
|
+
}
|
|
4976
|
+
for (const entry of entries) {
|
|
4977
|
+
if (!entry.isDirectory()) continue;
|
|
4978
|
+
if (active.has(entry.name)) continue;
|
|
4979
|
+
const path = join$1(STAGED_HOMES_DIR, entry.name);
|
|
4980
|
+
try {
|
|
4981
|
+
await rm(path, { recursive: true, force: true });
|
|
4982
|
+
removed.push(entry.name);
|
|
4983
|
+
} catch {
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
return { removed };
|
|
4987
|
+
}
|
|
4942
4988
|
async function copyDirRecursive(src, dest) {
|
|
4943
4989
|
const srcStat = await stat(src);
|
|
4944
4990
|
if (!srcStat.isDirectory()) return;
|
|
@@ -4958,7 +5004,8 @@ async function copyDirRecursive(src, dest) {
|
|
|
4958
5004
|
var credentialStaging = /*#__PURE__*/Object.freeze({
|
|
4959
5005
|
__proto__: null,
|
|
4960
5006
|
sanitizeEnvForSharing: sanitizeEnvForSharing,
|
|
4961
|
-
stageCredentialsForSharing: stageCredentialsForSharing
|
|
5007
|
+
stageCredentialsForSharing: stageCredentialsForSharing,
|
|
5008
|
+
sweepOrphanedStagedHomes: sweepOrphanedStagedHomes
|
|
4962
5009
|
});
|
|
4963
5010
|
|
|
4964
5011
|
function shouldIsolate(input) {
|
|
@@ -5125,6 +5172,287 @@ var claudeAuth = /*#__PURE__*/Object.freeze({
|
|
|
5125
5172
|
updateEnvFile: updateEnvFile
|
|
5126
5173
|
});
|
|
5127
5174
|
|
|
5175
|
+
const DEFAULT_WEB_BASE = "https://svamp.hypha.aicell.io";
|
|
5176
|
+
function getShareBaseUrl() {
|
|
5177
|
+
const fromEnv = (process.env.SVAMP_WEB_URL || "").trim();
|
|
5178
|
+
const base = fromEnv || DEFAULT_WEB_BASE;
|
|
5179
|
+
return base.replace(/\/+$/, "");
|
|
5180
|
+
}
|
|
5181
|
+
function buildSessionShareUrl(input) {
|
|
5182
|
+
const params = new URLSearchParams();
|
|
5183
|
+
if (input.workspace) params.set("ws", input.workspace);
|
|
5184
|
+
if (input.machineServiceId) params.set("svc", input.machineServiceId);
|
|
5185
|
+
const query = params.toString();
|
|
5186
|
+
return `${getShareBaseUrl()}/share/${input.sessionId}${query ? `?${query}` : ""}`;
|
|
5187
|
+
}
|
|
5188
|
+
function buildMachineShareUrl(input) {
|
|
5189
|
+
const params = new URLSearchParams();
|
|
5190
|
+
if (input.workspace) params.set("ws", input.workspace);
|
|
5191
|
+
const query = params.toString();
|
|
5192
|
+
return `${getShareBaseUrl()}/share/machine/${input.machineId}${query ? `?${query}` : ""}`;
|
|
5193
|
+
}
|
|
5194
|
+
const DAEMON_SHARE_EMAILS_ENV = "SVAMP_SHARE_EMAILS";
|
|
5195
|
+
function parseDaemonShareEmails(raw) {
|
|
5196
|
+
if (!raw) return [];
|
|
5197
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5198
|
+
const out = [];
|
|
5199
|
+
for (const part of raw.split(",")) {
|
|
5200
|
+
const email = part.trim();
|
|
5201
|
+
if (!email || !email.includes("@")) continue;
|
|
5202
|
+
const k = email.toLowerCase();
|
|
5203
|
+
if (seen.has(k)) continue;
|
|
5204
|
+
seen.add(k);
|
|
5205
|
+
out.push(email);
|
|
5206
|
+
}
|
|
5207
|
+
return out;
|
|
5208
|
+
}
|
|
5209
|
+
function applyDaemonShareSeed(sharing, seedEmails, ownerEmail) {
|
|
5210
|
+
const base = sharing ? { ...sharing, allowedUsers: [...sharing.allowedUsers || []] } : { enabled: true, owner: ownerEmail || "", allowedUsers: [] };
|
|
5211
|
+
base.enabled = true;
|
|
5212
|
+
if (!base.owner && ownerEmail) base.owner = ownerEmail;
|
|
5213
|
+
const existing = new Set(base.allowedUsers.map((u) => u.email.toLowerCase()));
|
|
5214
|
+
const addedEmails = [];
|
|
5215
|
+
for (const email of seedEmails) {
|
|
5216
|
+
if (existing.has(email.toLowerCase())) continue;
|
|
5217
|
+
base.allowedUsers.push(
|
|
5218
|
+
normalizeAllowedUser({ email, addedBy: "daemon-share-flag" }, base.owner || "daemon-share-flag")
|
|
5219
|
+
);
|
|
5220
|
+
existing.add(email.toLowerCase());
|
|
5221
|
+
addedEmails.push(email);
|
|
5222
|
+
}
|
|
5223
|
+
return { sharing: base, addedEmails };
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
const AUTH0_NAMESPACES = ["https://api.imjoy.io/", "https://amun.ai/"];
|
|
5227
|
+
const COOKIE_NAME = "svamp_serve_token";
|
|
5228
|
+
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
5229
|
+
const DEFAULT_CACHE_MAX_SIZE = 1e3;
|
|
5230
|
+
class TokenCache {
|
|
5231
|
+
cache = /* @__PURE__ */ new Map();
|
|
5232
|
+
ttlMs;
|
|
5233
|
+
maxSize;
|
|
5234
|
+
constructor(ttlMs = DEFAULT_CACHE_TTL_MS, maxSize = DEFAULT_CACHE_MAX_SIZE) {
|
|
5235
|
+
this.ttlMs = ttlMs;
|
|
5236
|
+
this.maxSize = maxSize;
|
|
5237
|
+
}
|
|
5238
|
+
get(token) {
|
|
5239
|
+
const info = this.cache.get(token);
|
|
5240
|
+
if (!info) return null;
|
|
5241
|
+
if (Date.now() > info.expiresAt) {
|
|
5242
|
+
this.cache.delete(token);
|
|
5243
|
+
return null;
|
|
5244
|
+
}
|
|
5245
|
+
return info;
|
|
5246
|
+
}
|
|
5247
|
+
set(token, email) {
|
|
5248
|
+
if (this.cache.size >= this.maxSize) {
|
|
5249
|
+
const oldest = this.cache.keys().next().value;
|
|
5250
|
+
if (oldest) this.cache.delete(oldest);
|
|
5251
|
+
}
|
|
5252
|
+
this.cache.set(token, {
|
|
5253
|
+
email,
|
|
5254
|
+
expiresAt: Date.now() + this.ttlMs
|
|
5255
|
+
});
|
|
5256
|
+
}
|
|
5257
|
+
clear() {
|
|
5258
|
+
this.cache.clear();
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
function hasCookieToken(req) {
|
|
5262
|
+
return !!parseCookies(req.headers.cookie || "")[COOKIE_NAME];
|
|
5263
|
+
}
|
|
5264
|
+
function extractToken(req) {
|
|
5265
|
+
const cookies = parseCookies(req.headers.cookie || "");
|
|
5266
|
+
if (cookies[COOKIE_NAME]) return cookies[COOKIE_NAME];
|
|
5267
|
+
const auth = req.headers.authorization;
|
|
5268
|
+
if (auth?.startsWith("Bearer ") || auth?.startsWith("bearer ")) {
|
|
5269
|
+
return auth.slice(7).trim();
|
|
5270
|
+
}
|
|
5271
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
5272
|
+
const qToken = url.searchParams.get("token");
|
|
5273
|
+
if (qToken) return qToken;
|
|
5274
|
+
return null;
|
|
5275
|
+
}
|
|
5276
|
+
function parseCookies(header) {
|
|
5277
|
+
const cookies = {};
|
|
5278
|
+
for (const pair of header.split(";")) {
|
|
5279
|
+
const eq = pair.indexOf("=");
|
|
5280
|
+
if (eq > 0) {
|
|
5281
|
+
const key = pair.slice(0, eq).trim();
|
|
5282
|
+
const value = pair.slice(eq + 1).trim();
|
|
5283
|
+
cookies[key] = value;
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
return cookies;
|
|
5287
|
+
}
|
|
5288
|
+
function parseJwtEmail(token) {
|
|
5289
|
+
try {
|
|
5290
|
+
const parts = token.split(".");
|
|
5291
|
+
if (parts.length !== 3) return null;
|
|
5292
|
+
const payload = JSON.parse(
|
|
5293
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
5294
|
+
);
|
|
5295
|
+
if (payload.exp && payload.exp * 1e3 < Date.now()) return null;
|
|
5296
|
+
for (const ns of AUTH0_NAMESPACES) {
|
|
5297
|
+
const email = payload[ns + "email"];
|
|
5298
|
+
if (typeof email === "string" && email) return email.toLowerCase();
|
|
5299
|
+
}
|
|
5300
|
+
if (typeof payload.email === "string" && payload.email) return payload.email.toLowerCase();
|
|
5301
|
+
return null;
|
|
5302
|
+
} catch {
|
|
5303
|
+
return null;
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
async function verifyTokenViaHypha(token, hyphaServerUrl) {
|
|
5307
|
+
try {
|
|
5308
|
+
const baseUrl = hyphaServerUrl.replace(/\/$/, "");
|
|
5309
|
+
const url = `${baseUrl}/public/services/ws/get_user_info`;
|
|
5310
|
+
const resp = await fetch(url, {
|
|
5311
|
+
method: "GET",
|
|
5312
|
+
headers: {
|
|
5313
|
+
Authorization: `Bearer ${token}`
|
|
5314
|
+
},
|
|
5315
|
+
signal: AbortSignal.timeout(1e4)
|
|
5316
|
+
});
|
|
5317
|
+
if (!resp.ok) return null;
|
|
5318
|
+
const data = await resp.json();
|
|
5319
|
+
const email = data?.email;
|
|
5320
|
+
if (typeof email === "string" && email) return email.toLowerCase();
|
|
5321
|
+
return null;
|
|
5322
|
+
} catch {
|
|
5323
|
+
return null;
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
class ServeAuth {
|
|
5327
|
+
cache;
|
|
5328
|
+
hyphaServerUrl;
|
|
5329
|
+
constructor(options) {
|
|
5330
|
+
this.hyphaServerUrl = options.hyphaServerUrl.replace(/\/$/, "");
|
|
5331
|
+
this.cache = new TokenCache(
|
|
5332
|
+
options.cacheTtlMs || DEFAULT_CACHE_TTL_MS,
|
|
5333
|
+
options.cacheMaxSize || DEFAULT_CACHE_MAX_SIZE
|
|
5334
|
+
);
|
|
5335
|
+
}
|
|
5336
|
+
/**
|
|
5337
|
+
* Authenticate a request and return the user's email, or null if not authenticated.
|
|
5338
|
+
*/
|
|
5339
|
+
async authenticate(req) {
|
|
5340
|
+
const token = extractToken(req);
|
|
5341
|
+
if (!token) return null;
|
|
5342
|
+
const cached = this.cache.get(token);
|
|
5343
|
+
if (cached) return cached.email;
|
|
5344
|
+
const localEmail = parseJwtEmail(token);
|
|
5345
|
+
if (localEmail) {
|
|
5346
|
+
this.cache.set(token, localEmail);
|
|
5347
|
+
return localEmail;
|
|
5348
|
+
}
|
|
5349
|
+
const serverEmail = await verifyTokenViaHypha(token, this.hyphaServerUrl);
|
|
5350
|
+
if (serverEmail) {
|
|
5351
|
+
this.cache.set(token, serverEmail);
|
|
5352
|
+
return serverEmail;
|
|
5353
|
+
}
|
|
5354
|
+
return null;
|
|
5355
|
+
}
|
|
5356
|
+
/**
|
|
5357
|
+
* Check if a user email is authorized for a mount's access level.
|
|
5358
|
+
*/
|
|
5359
|
+
isAuthorized(email, access, ownerEmail) {
|
|
5360
|
+
if (access === "public") return true;
|
|
5361
|
+
if (!email) return false;
|
|
5362
|
+
if (access === "owner") {
|
|
5363
|
+
return !!ownerEmail && email.toLowerCase() === ownerEmail.toLowerCase();
|
|
5364
|
+
}
|
|
5365
|
+
return access.some((e) => e.toLowerCase() === email.toLowerCase());
|
|
5366
|
+
}
|
|
5367
|
+
/**
|
|
5368
|
+
* Generate the login page HTML. Loads the `hypha-rpc` JS SDK from CDN and
|
|
5369
|
+
* calls `hyphaWebsocketClient.login({ server_url, login_callback })`, which
|
|
5370
|
+
* handles opening the login URL, polling, and token retrieval internally.
|
|
5371
|
+
* Matches the pattern used by bioimage.io — proven to work.
|
|
5372
|
+
*/
|
|
5373
|
+
getLoginPageHtml(redirectUrl) {
|
|
5374
|
+
return `<!DOCTYPE html>
|
|
5375
|
+
<html><head>
|
|
5376
|
+
<meta charset="utf-8">
|
|
5377
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5378
|
+
<title>Sign in \u2014 Svamp File Server</title>
|
|
5379
|
+
<style>
|
|
5380
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
5381
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#f6f8fa;color:#24292f;padding:16px}
|
|
5382
|
+
.card{background:#fff;border:1px solid #d0d7de;border-radius:12px;padding:32px;max-width:420px;width:100%;text-align:center;box-shadow:0 4px 12px rgba(31,35,40,0.06)}
|
|
5383
|
+
h1{font-size:1.25rem;margin-bottom:8px}
|
|
5384
|
+
.subtitle{color:#656d76;font-size:0.9rem;margin-bottom:24px}
|
|
5385
|
+
button{background:#0969da;color:#fff;border:none;border-radius:8px;padding:12px 24px;font-size:1rem;cursor:pointer;font-weight:500;width:100%}
|
|
5386
|
+
button:hover{background:#0860c4}
|
|
5387
|
+
button:disabled{background:#94d3a2;cursor:wait}
|
|
5388
|
+
.status{margin-top:16px;color:#656d76;font-size:0.85rem;min-height:20px;word-break:break-word}
|
|
5389
|
+
.error{color:#cf222e}
|
|
5390
|
+
.ok{color:#1a7f37}
|
|
5391
|
+
a{color:#0969da;text-decoration:none}
|
|
5392
|
+
a:hover{text-decoration:underline}
|
|
5393
|
+
</style>
|
|
5394
|
+
<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.21.28/dist/hypha-rpc-websocket.min.js"><\/script>
|
|
5395
|
+
</head><body>
|
|
5396
|
+
<div class="card">
|
|
5397
|
+
<h1>Sign in required</h1>
|
|
5398
|
+
<p class="subtitle">This resource is private. Sign in with your Hypha account to continue.</p>
|
|
5399
|
+
<button id="login-btn">Sign in with Hypha</button>
|
|
5400
|
+
<div class="status" id="status"></div>
|
|
5401
|
+
</div>
|
|
5402
|
+
<script>
|
|
5403
|
+
const hyphaServer = ${JSON.stringify(this.hyphaServerUrl)};
|
|
5404
|
+
const redirectUrl = ${JSON.stringify(redirectUrl)};
|
|
5405
|
+
const cookieName = ${JSON.stringify(COOKIE_NAME)};
|
|
5406
|
+
|
|
5407
|
+
const btn = document.getElementById('login-btn');
|
|
5408
|
+
const statusEl = document.getElementById('status');
|
|
5409
|
+
|
|
5410
|
+
function setError(msg) {
|
|
5411
|
+
statusEl.innerHTML = '<span class="error">' + msg + '</span>';
|
|
5412
|
+
btn.disabled = false;
|
|
5413
|
+
}
|
|
5414
|
+
|
|
5415
|
+
btn.addEventListener('click', async () => {
|
|
5416
|
+
if (typeof hyphaWebsocketClient === 'undefined' || !hyphaWebsocketClient.login) {
|
|
5417
|
+
setError('Hypha SDK failed to load. Check your network connection and retry.');
|
|
5418
|
+
return;
|
|
5419
|
+
}
|
|
5420
|
+
|
|
5421
|
+
btn.disabled = true;
|
|
5422
|
+
statusEl.textContent = 'Opening Hypha sign-in\u2026';
|
|
5423
|
+
|
|
5424
|
+
try {
|
|
5425
|
+
const token = await hyphaWebsocketClient.login({
|
|
5426
|
+
server_url: hyphaServer,
|
|
5427
|
+
login_callback: (context) => {
|
|
5428
|
+
statusEl.textContent = 'Waiting for you to sign in\u2026';
|
|
5429
|
+
// Open the login URL in a new tab. Called synchronously from
|
|
5430
|
+
// inside the SDK's login() after the user's button click, so
|
|
5431
|
+
// popup blockers generally allow it.
|
|
5432
|
+
window.open(context.login_url, '_blank');
|
|
5433
|
+
},
|
|
5434
|
+
});
|
|
5435
|
+
|
|
5436
|
+
if (!token) throw new Error('No token returned from login');
|
|
5437
|
+
|
|
5438
|
+
// Set the cookie so subsequent requests to this origin are authenticated.
|
|
5439
|
+
const secure = location.protocol === 'https:' ? '; Secure' : '';
|
|
5440
|
+
document.cookie = cookieName + '=' + token + '; path=/; SameSite=Lax' + secure;
|
|
5441
|
+
|
|
5442
|
+
statusEl.innerHTML = '<span class="ok">Signed in. Redirecting\u2026</span>';
|
|
5443
|
+
setTimeout(() => { window.location.replace(redirectUrl); }, 300);
|
|
5444
|
+
} catch (err) {
|
|
5445
|
+
setError('Login failed: ' + (err && err.message ? err.message : err));
|
|
5446
|
+
}
|
|
5447
|
+
});
|
|
5448
|
+
<\/script>
|
|
5449
|
+
</body></html>`;
|
|
5450
|
+
}
|
|
5451
|
+
destroy() {
|
|
5452
|
+
this.cache.clear();
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5128
5456
|
const DEFAULT_PROBE_INTERVAL_S = 10;
|
|
5129
5457
|
const DEFAULT_PROBE_TIMEOUT_S = 5;
|
|
5130
5458
|
const DEFAULT_PROBE_FAILURE_THRESHOLD = 3;
|
|
@@ -6913,7 +7241,7 @@ async function startDaemon(options) {
|
|
|
6913
7241
|
const supervisor = new ProcessSupervisor(join(SVAMP_HOME, "processes"));
|
|
6914
7242
|
await supervisor.init();
|
|
6915
7243
|
const tunnels = /* @__PURE__ */ new Map();
|
|
6916
|
-
const { ServeManager } = await import('./serveManager-
|
|
7244
|
+
const { ServeManager } = await import('./serveManager-BhWfboEx.mjs');
|
|
6917
7245
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
6918
7246
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
6919
7247
|
});
|
|
@@ -9164,6 +9492,29 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
9164
9492
|
if (persistedMachineMeta) {
|
|
9165
9493
|
logger.log(`Restored machine metadata (sharing=${!!persistedMachineMeta.sharing}, securityContextConfig=${!!persistedMachineMeta.securityContextConfig}, injectPlatformGuidance=${persistedMachineMeta.injectPlatformGuidance})`);
|
|
9166
9494
|
}
|
|
9495
|
+
let seededSharing = persistedMachineMeta?.sharing;
|
|
9496
|
+
const seedEmails = parseDaemonShareEmails(process.env[DAEMON_SHARE_EMAILS_ENV]);
|
|
9497
|
+
if (seedEmails.length > 0) {
|
|
9498
|
+
const ownerEmail = parseJwtEmail(process.env.HYPHA_TOKEN || "") || void 0;
|
|
9499
|
+
const result = applyDaemonShareSeed(seededSharing, seedEmails, ownerEmail);
|
|
9500
|
+
seededSharing = result.sharing;
|
|
9501
|
+
if (result.addedEmails.length > 0) {
|
|
9502
|
+
logger.log(`Seeded machine sharing with ${result.addedEmails.length} user(s): ${result.addedEmails.join(", ")}`);
|
|
9503
|
+
} else {
|
|
9504
|
+
logger.log(`Daemon --share seed: no new users (all already in allowedUsers).`);
|
|
9505
|
+
}
|
|
9506
|
+
savePersistedMachineMetadata(SVAMP_HOME, {
|
|
9507
|
+
sharing: seededSharing,
|
|
9508
|
+
securityContextConfig: persistedMachineMeta?.securityContextConfig,
|
|
9509
|
+
injectPlatformGuidance: persistedMachineMeta?.injectPlatformGuidance
|
|
9510
|
+
});
|
|
9511
|
+
try {
|
|
9512
|
+
updateEnvFile({ [DAEMON_SHARE_EMAILS_ENV]: void 0 });
|
|
9513
|
+
process.env[DAEMON_SHARE_EMAILS_ENV] = "";
|
|
9514
|
+
} catch (err) {
|
|
9515
|
+
logger.log(`[share-seed] Failed to clear ${DAEMON_SHARE_EMAILS_ENV} from .env: ${err.message}`);
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9167
9518
|
const machineMetadata = {
|
|
9168
9519
|
host: os$1.hostname(),
|
|
9169
9520
|
platform: os$1.platform(),
|
|
@@ -9173,8 +9524,9 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
9173
9524
|
svampLibDir: join(__dirname$1, ".."),
|
|
9174
9525
|
displayName: process.env.SVAMP_DISPLAY_NAME || void 0,
|
|
9175
9526
|
isolationCapabilities,
|
|
9176
|
-
// Restore persisted sharing
|
|
9177
|
-
|
|
9527
|
+
// Restore persisted sharing (possibly augmented with --share seed above),
|
|
9528
|
+
// security context config, and platform guidance flag.
|
|
9529
|
+
...seededSharing && { sharing: seededSharing },
|
|
9178
9530
|
...persistedMachineMeta?.securityContextConfig && { securityContextConfig: persistedMachineMeta.securityContextConfig },
|
|
9179
9531
|
...persistedMachineMeta?.injectPlatformGuidance !== void 0 && { injectPlatformGuidance: persistedMachineMeta.injectPlatformGuidance }
|
|
9180
9532
|
};
|
|
@@ -9223,6 +9575,15 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
9223
9575
|
}
|
|
9224
9576
|
);
|
|
9225
9577
|
logger.log(`Machine service registered: svamp-machine-${machineId}`);
|
|
9578
|
+
if (seededSharing?.enabled) {
|
|
9579
|
+
const shareUrl = buildMachineShareUrl({
|
|
9580
|
+
machineId,
|
|
9581
|
+
workspace: server.config?.workspace
|
|
9582
|
+
});
|
|
9583
|
+
logger.log(`Machine share URL: ${shareUrl}`);
|
|
9584
|
+
const userList = (seededSharing.allowedUsers || []).map((u) => u.email).join(", ");
|
|
9585
|
+
if (userList) logger.log(` Shared with: ${userList}`);
|
|
9586
|
+
}
|
|
9226
9587
|
machineServiceRef = machineService;
|
|
9227
9588
|
const artifactSync = new SessionArtifactSync(server, logger.log);
|
|
9228
9589
|
const debugService = await registerDebugService(server, machineId, {
|
|
@@ -9243,6 +9604,15 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
9243
9604
|
logger.log(`Debug service registered: svamp-debug-${machineId}`);
|
|
9244
9605
|
await serveManager.restore();
|
|
9245
9606
|
const persistedSessions = loadPersistedSessions();
|
|
9607
|
+
try {
|
|
9608
|
+
const knownIds = new Set(persistedSessions.map((s) => s.sessionId).concat(Object.keys(loadSessionIndex())));
|
|
9609
|
+
const { removed } = await sweepOrphanedStagedHomes(knownIds);
|
|
9610
|
+
if (removed.length > 0) {
|
|
9611
|
+
logger.log(`Cleaned up ${removed.length} orphaned staged home(s) from prior runs.`);
|
|
9612
|
+
}
|
|
9613
|
+
} catch (err) {
|
|
9614
|
+
logger.log(`[staged-homes] sweep failed: ${err.message}`);
|
|
9615
|
+
}
|
|
9246
9616
|
const sessionsToAutoContinue = [];
|
|
9247
9617
|
const sessionsToRalphResume = [];
|
|
9248
9618
|
if (persistedSessions.length > 0) {
|
|
@@ -9922,4 +10292,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
9922
10292
|
stopDaemon: stopDaemon
|
|
9923
10293
|
});
|
|
9924
10294
|
|
|
9925
|
-
export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g,
|
|
10295
|
+
export { DefaultTransport$1 as D, GeminiTransport$1 as G, ServeAuth as S, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g, hasCookieToken as h, buildSessionShareUrl as i, buildMachineShareUrl as j, generateHookSettings as k, loadSecurityContextConfig as l, mergeSecurityContexts as m, normalizeAllowedUser as n, acpBackend as o, acpAgentConfig as p, codexMcpBackend as q, registerMachineService as r, startDaemon as s, claudeAuth as t, run as u };
|
|
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { existsSync, readFileSync, watch } from 'node:fs';
|
|
5
|
-
import { c as connectToHypha, a as registerSessionService,
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService, k as generateHookSettings } from './run-V2qpcN7f.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
@@ -54,7 +54,7 @@ async function handleServeCommand() {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
async function serveAdd(args, machineId) {
|
|
57
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
57
|
+
const { connectAndGetMachine } = await import('./commands-CTQAVadm.mjs');
|
|
58
58
|
const pos = positionalArgs(args);
|
|
59
59
|
const name = pos[0];
|
|
60
60
|
if (!name) {
|
|
@@ -86,7 +86,7 @@ async function serveAdd(args, machineId) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
async function serveApply(args, machineId) {
|
|
89
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
89
|
+
const { connectAndGetMachine } = await import('./commands-CTQAVadm.mjs');
|
|
90
90
|
const fs = await import('fs');
|
|
91
91
|
const yaml = await import('yaml');
|
|
92
92
|
const file = positionalArgs(args)[0];
|
|
@@ -171,7 +171,7 @@ async function serveApply(args, machineId) {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
async function serveRemove(args, machineId) {
|
|
174
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
174
|
+
const { connectAndGetMachine } = await import('./commands-CTQAVadm.mjs');
|
|
175
175
|
const pos = positionalArgs(args);
|
|
176
176
|
const name = pos[0];
|
|
177
177
|
if (!name) {
|
|
@@ -191,7 +191,7 @@ async function serveRemove(args, machineId) {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
async function serveList(args, machineId) {
|
|
194
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
194
|
+
const { connectAndGetMachine } = await import('./commands-CTQAVadm.mjs');
|
|
195
195
|
const all = hasFlag(args, "--all", "-a");
|
|
196
196
|
const json = hasFlag(args, "--json");
|
|
197
197
|
const sessionId = getFlag(args, "--session");
|
|
@@ -224,7 +224,7 @@ async function serveList(args, machineId) {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
async function serveInfo(machineId) {
|
|
227
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
227
|
+
const { connectAndGetMachine } = await import('./commands-CTQAVadm.mjs');
|
|
228
228
|
const { machine, server } = await connectAndGetMachine(machineId);
|
|
229
229
|
try {
|
|
230
230
|
const info = await machine.serveInfo();
|