@vellumai/cli 0.7.1 → 0.7.2
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/AGENTS.md +3 -11
- package/bun.lock +0 -15
- package/package.json +1 -6
- package/src/__tests__/backup.test.ts +121 -5
- package/src/__tests__/teleport.test.ts +512 -10
- package/src/commands/backup.ts +35 -2
- package/src/commands/client.ts +47 -6
- package/src/commands/events.ts +3 -0
- package/src/commands/exec.ts +13 -4
- package/src/commands/hatch.ts +1 -1
- package/src/commands/restore.ts +7 -1
- package/src/commands/setup.ts +38 -73
- package/src/commands/teleport.ts +111 -11
- package/src/commands/upgrade.ts +6 -0
- package/src/commands/wake.ts +5 -16
- package/src/components/DefaultMainScreen.tsx +40 -126
- package/src/index.ts +1 -7
- package/src/lib/__tests__/docker.test.ts +50 -32
- package/src/lib/__tests__/local-runtime-client.test.ts +186 -0
- package/src/lib/__tests__/platform-client-signed-url.test.ts +235 -0
- package/src/lib/__tests__/runtime-url.test.ts +39 -1
- package/src/lib/assistant-client.ts +13 -5
- package/src/lib/assistant-config.ts +0 -25
- package/src/lib/backup-ops.ts +43 -17
- package/src/lib/docker-statefulset.ts +381 -0
- package/src/lib/docker.ts +8 -247
- package/src/lib/guardian-token.ts +56 -6
- package/src/lib/hatch-local.ts +3 -26
- package/src/lib/local-runtime-client.ts +82 -1
- package/src/lib/local.ts +9 -7
- package/src/lib/ngrok.ts +36 -26
- package/src/lib/platform-client.ts +96 -1
- package/src/lib/retire-local.ts +2 -2
- package/src/lib/runtime-url.ts +22 -0
- package/src/lib/upgrade-lifecycle.ts +65 -0
- package/src/commands/pair.ts +0 -212
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import {
|
|
3
|
-
import { hostname, userInfo } from "os";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
4
3
|
import { basename } from "path";
|
|
5
|
-
import qrcode from "qrcode-terminal";
|
|
6
4
|
import {
|
|
7
5
|
useCallback,
|
|
8
6
|
useEffect,
|
|
@@ -46,7 +44,6 @@ export const SLASH_COMMANDS = [
|
|
|
46
44
|
"/doctor",
|
|
47
45
|
"/exit",
|
|
48
46
|
"/help",
|
|
49
|
-
"/pair",
|
|
50
47
|
"/q",
|
|
51
48
|
"/quit",
|
|
52
49
|
"/retire",
|
|
@@ -173,14 +170,14 @@ async function runtimeRequest<T>(
|
|
|
173
170
|
assistantId: string,
|
|
174
171
|
path: string,
|
|
175
172
|
init?: RequestInit,
|
|
176
|
-
|
|
173
|
+
auth?: Record<string, string>,
|
|
177
174
|
): Promise<T> {
|
|
178
175
|
const url = `${baseUrl}/v1/assistants/${assistantId}${path}`;
|
|
179
176
|
const response = await fetch(url, {
|
|
180
177
|
...init,
|
|
181
178
|
headers: {
|
|
182
179
|
"Content-Type": "application/json",
|
|
183
|
-
...
|
|
180
|
+
...auth,
|
|
184
181
|
...(init?.headers as Record<string, string> | undefined),
|
|
185
182
|
},
|
|
186
183
|
});
|
|
@@ -219,7 +216,7 @@ async function checkHealthRuntime(baseUrl: string): Promise<HealthResponse> {
|
|
|
219
216
|
async function pollMessages(
|
|
220
217
|
baseUrl: string,
|
|
221
218
|
assistantId: string,
|
|
222
|
-
|
|
219
|
+
auth?: Record<string, string>,
|
|
223
220
|
): Promise<ListMessagesResponse> {
|
|
224
221
|
const params = new URLSearchParams({ conversationKey: assistantId });
|
|
225
222
|
return runtimeRequest<ListMessagesResponse>(
|
|
@@ -227,7 +224,7 @@ async function pollMessages(
|
|
|
227
224
|
assistantId,
|
|
228
225
|
`/messages?${params.toString()}`,
|
|
229
226
|
undefined,
|
|
230
|
-
|
|
227
|
+
auth,
|
|
231
228
|
);
|
|
232
229
|
}
|
|
233
230
|
|
|
@@ -236,7 +233,7 @@ async function sendMessage(
|
|
|
236
233
|
assistantId: string,
|
|
237
234
|
content: string,
|
|
238
235
|
signal?: AbortSignal,
|
|
239
|
-
|
|
236
|
+
auth?: Record<string, string>,
|
|
240
237
|
): Promise<SendMessageResponse> {
|
|
241
238
|
return runtimeRequest<SendMessageResponse>(
|
|
242
239
|
baseUrl,
|
|
@@ -252,7 +249,7 @@ async function sendMessage(
|
|
|
252
249
|
}),
|
|
253
250
|
signal,
|
|
254
251
|
},
|
|
255
|
-
|
|
252
|
+
auth,
|
|
256
253
|
);
|
|
257
254
|
}
|
|
258
255
|
|
|
@@ -261,7 +258,7 @@ async function submitDecision(
|
|
|
261
258
|
assistantId: string,
|
|
262
259
|
requestId: string,
|
|
263
260
|
decision: "allow" | "deny",
|
|
264
|
-
|
|
261
|
+
auth?: Record<string, string>,
|
|
265
262
|
): Promise<SubmitDecisionResponse> {
|
|
266
263
|
return runtimeRequest<SubmitDecisionResponse>(
|
|
267
264
|
baseUrl,
|
|
@@ -271,7 +268,7 @@ async function submitDecision(
|
|
|
271
268
|
method: "POST",
|
|
272
269
|
body: JSON.stringify({ requestId, decision }),
|
|
273
270
|
},
|
|
274
|
-
|
|
271
|
+
auth,
|
|
275
272
|
);
|
|
276
273
|
}
|
|
277
274
|
|
|
@@ -282,7 +279,7 @@ async function addTrustRule(
|
|
|
282
279
|
pattern: string,
|
|
283
280
|
scope: string,
|
|
284
281
|
decision: "allow" | "deny",
|
|
285
|
-
|
|
282
|
+
auth?: Record<string, string>,
|
|
286
283
|
): Promise<AddTrustRuleResponse> {
|
|
287
284
|
return runtimeRequest<AddTrustRuleResponse>(
|
|
288
285
|
baseUrl,
|
|
@@ -292,7 +289,7 @@ async function addTrustRule(
|
|
|
292
289
|
method: "POST",
|
|
293
290
|
body: JSON.stringify({ requestId, pattern, scope, decision }),
|
|
294
291
|
},
|
|
295
|
-
|
|
292
|
+
auth,
|
|
296
293
|
);
|
|
297
294
|
}
|
|
298
295
|
|
|
@@ -351,7 +348,7 @@ async function* streamEvents(
|
|
|
351
348
|
assistantId: string,
|
|
352
349
|
conversationKey: string,
|
|
353
350
|
signal: AbortSignal,
|
|
354
|
-
|
|
351
|
+
auth?: Record<string, string>,
|
|
355
352
|
): AsyncGenerator<SseEvent> {
|
|
356
353
|
const params = new URLSearchParams({ conversationKey });
|
|
357
354
|
const url = `${baseUrl}/v1/assistants/${assistantId}/events?${params.toString()}`;
|
|
@@ -360,7 +357,7 @@ async function* streamEvents(
|
|
|
360
357
|
const response = await fetch(url, {
|
|
361
358
|
headers: {
|
|
362
359
|
Accept: "text/event-stream",
|
|
363
|
-
...
|
|
360
|
+
...auth,
|
|
364
361
|
...clientHeaders,
|
|
365
362
|
},
|
|
366
363
|
signal,
|
|
@@ -457,7 +454,7 @@ async function handleConfirmationPrompt(
|
|
|
457
454
|
requestId: string,
|
|
458
455
|
confirmation: PendingConfirmation,
|
|
459
456
|
chatApp: ChatAppHandle,
|
|
460
|
-
|
|
457
|
+
auth?: Record<string, string>,
|
|
461
458
|
): Promise<void> {
|
|
462
459
|
const preview = formatConfirmationPreview(
|
|
463
460
|
confirmation.toolName,
|
|
@@ -483,7 +480,7 @@ async function handleConfirmationPrompt(
|
|
|
483
480
|
const index = await chatApp.showSelection("Tool Approval", options);
|
|
484
481
|
|
|
485
482
|
if (index === 0) {
|
|
486
|
-
await submitDecision(baseUrl, assistantId, requestId, "allow",
|
|
483
|
+
await submitDecision(baseUrl, assistantId, requestId, "allow", auth);
|
|
487
484
|
chatApp.addStatus("\u2714 Allowed", "green");
|
|
488
485
|
return;
|
|
489
486
|
}
|
|
@@ -495,7 +492,7 @@ async function handleConfirmationPrompt(
|
|
|
495
492
|
confirmation,
|
|
496
493
|
chatApp,
|
|
497
494
|
"always_allow",
|
|
498
|
-
|
|
495
|
+
auth,
|
|
499
496
|
);
|
|
500
497
|
return;
|
|
501
498
|
}
|
|
@@ -507,12 +504,12 @@ async function handleConfirmationPrompt(
|
|
|
507
504
|
confirmation,
|
|
508
505
|
chatApp,
|
|
509
506
|
"always_deny",
|
|
510
|
-
|
|
507
|
+
auth,
|
|
511
508
|
);
|
|
512
509
|
return;
|
|
513
510
|
}
|
|
514
511
|
|
|
515
|
-
await submitDecision(baseUrl, assistantId, requestId, "deny",
|
|
512
|
+
await submitDecision(baseUrl, assistantId, requestId, "deny", auth);
|
|
516
513
|
chatApp.addStatus("\u2718 Denied", "yellow");
|
|
517
514
|
}
|
|
518
515
|
|
|
@@ -523,7 +520,7 @@ async function handlePatternSelection(
|
|
|
523
520
|
confirmation: PendingConfirmation,
|
|
524
521
|
chatApp: ChatAppHandle,
|
|
525
522
|
trustDecision: TrustDecision,
|
|
526
|
-
|
|
523
|
+
auth?: Record<string, string>,
|
|
527
524
|
): Promise<void> {
|
|
528
525
|
const allowlistOptions = confirmation.allowlistOptions ?? [];
|
|
529
526
|
const label = trustDecision === "always_deny" ? "Denylist" : "Allowlist";
|
|
@@ -544,12 +541,12 @@ async function handlePatternSelection(
|
|
|
544
541
|
chatApp,
|
|
545
542
|
selectedPattern,
|
|
546
543
|
trustDecision,
|
|
547
|
-
|
|
544
|
+
auth,
|
|
548
545
|
);
|
|
549
546
|
return;
|
|
550
547
|
}
|
|
551
548
|
|
|
552
|
-
await submitDecision(baseUrl, assistantId, requestId, "deny",
|
|
549
|
+
await submitDecision(baseUrl, assistantId, requestId, "deny", auth);
|
|
553
550
|
chatApp.addStatus("\u2718 Denied", "yellow");
|
|
554
551
|
}
|
|
555
552
|
|
|
@@ -561,7 +558,7 @@ async function handleScopeSelection(
|
|
|
561
558
|
chatApp: ChatAppHandle,
|
|
562
559
|
selectedPattern: string,
|
|
563
560
|
trustDecision: TrustDecision,
|
|
564
|
-
|
|
561
|
+
auth?: Record<string, string>,
|
|
565
562
|
): Promise<void> {
|
|
566
563
|
const scopeOptions = confirmation.scopeOptions ?? [];
|
|
567
564
|
const label = trustDecision === "always_deny" ? "Denylist" : "Allowlist";
|
|
@@ -578,14 +575,14 @@ async function handleScopeSelection(
|
|
|
578
575
|
selectedPattern,
|
|
579
576
|
scopeOptions[index].scope,
|
|
580
577
|
ruleDecision,
|
|
581
|
-
|
|
578
|
+
auth,
|
|
582
579
|
);
|
|
583
580
|
await submitDecision(
|
|
584
581
|
baseUrl,
|
|
585
582
|
assistantId,
|
|
586
583
|
requestId,
|
|
587
584
|
ruleDecision === "deny" ? "deny" : "allow",
|
|
588
|
-
|
|
585
|
+
auth,
|
|
589
586
|
);
|
|
590
587
|
const ruleLabel =
|
|
591
588
|
trustDecision === "always_deny" ? "Denylisted" : "Allowlisted";
|
|
@@ -597,7 +594,7 @@ async function handleScopeSelection(
|
|
|
597
594
|
return;
|
|
598
595
|
}
|
|
599
596
|
|
|
600
|
-
await submitDecision(baseUrl, assistantId, requestId, "deny",
|
|
597
|
+
await submitDecision(baseUrl, assistantId, requestId, "deny", auth);
|
|
601
598
|
chatApp.addStatus("\u2718 Denied", "yellow");
|
|
602
599
|
}
|
|
603
600
|
|
|
@@ -753,10 +750,6 @@ function HelpDisplay(): ReactElement {
|
|
|
753
750
|
{" /clear "}
|
|
754
751
|
<Text dimColor>Clear the screen</Text>
|
|
755
752
|
</Text>
|
|
756
|
-
<Text>
|
|
757
|
-
{" /pair "}
|
|
758
|
-
<Text dimColor>Generate a QR code for mobile device pairing</Text>
|
|
759
|
-
</Text>
|
|
760
753
|
<Text>
|
|
761
754
|
{" /help, ? "}
|
|
762
755
|
<Text dimColor>Show this help</Text>
|
|
@@ -1368,7 +1361,9 @@ interface ChatAppProps {
|
|
|
1368
1361
|
runtimeUrl: string;
|
|
1369
1362
|
assistantId: string;
|
|
1370
1363
|
species: Species;
|
|
1371
|
-
|
|
1364
|
+
/** Pre-built auth headers (e.g. { Authorization: "Bearer ..." } for local,
|
|
1365
|
+
* { "X-Session-Token": "...", "Vellum-Organization-Id": "..." } for platform). */
|
|
1366
|
+
auth?: Record<string, string>;
|
|
1372
1367
|
project?: string;
|
|
1373
1368
|
zone?: string;
|
|
1374
1369
|
onExit: () => void;
|
|
@@ -1379,7 +1374,7 @@ function ChatApp({
|
|
|
1379
1374
|
runtimeUrl,
|
|
1380
1375
|
assistantId,
|
|
1381
1376
|
species,
|
|
1382
|
-
|
|
1377
|
+
auth,
|
|
1383
1378
|
project,
|
|
1384
1379
|
zone,
|
|
1385
1380
|
onExit,
|
|
@@ -1692,7 +1687,7 @@ function ChatApp({
|
|
|
1692
1687
|
const historyResponse = await pollMessages(
|
|
1693
1688
|
runtimeUrl,
|
|
1694
1689
|
assistantId,
|
|
1695
|
-
|
|
1690
|
+
auth,
|
|
1696
1691
|
);
|
|
1697
1692
|
h.hideSpinner();
|
|
1698
1693
|
if (historyResponse.messages.length > 0) {
|
|
@@ -1717,7 +1712,7 @@ function ChatApp({
|
|
|
1717
1712
|
assistantId,
|
|
1718
1713
|
assistantId,
|
|
1719
1714
|
sseAc.signal,
|
|
1720
|
-
|
|
1715
|
+
auth,
|
|
1721
1716
|
)) {
|
|
1722
1717
|
const hRef = handleRef_.current;
|
|
1723
1718
|
if (!hRef) continue;
|
|
@@ -1788,7 +1783,7 @@ function ChatApp({
|
|
|
1788
1783
|
event.persistentDecisionsAllowed,
|
|
1789
1784
|
},
|
|
1790
1785
|
hRef,
|
|
1791
|
-
|
|
1786
|
+
auth,
|
|
1792
1787
|
);
|
|
1793
1788
|
hRef.showSpinner("Working...");
|
|
1794
1789
|
break;
|
|
@@ -1819,7 +1814,7 @@ function ChatApp({
|
|
|
1819
1814
|
delivery,
|
|
1820
1815
|
}),
|
|
1821
1816
|
},
|
|
1822
|
-
|
|
1817
|
+
auth,
|
|
1823
1818
|
);
|
|
1824
1819
|
},
|
|
1825
1820
|
);
|
|
@@ -1903,7 +1898,7 @@ function ChatApp({
|
|
|
1903
1898
|
);
|
|
1904
1899
|
return false;
|
|
1905
1900
|
}
|
|
1906
|
-
}, [runtimeUrl, assistantId,
|
|
1901
|
+
}, [runtimeUrl, assistantId, auth]);
|
|
1907
1902
|
|
|
1908
1903
|
const handleInput = useCallback(
|
|
1909
1904
|
async (input: string): Promise<void> => {
|
|
@@ -2110,9 +2105,7 @@ function ChatApp({
|
|
|
2110
2105
|
method: "POST",
|
|
2111
2106
|
headers: {
|
|
2112
2107
|
"Content-Type": "application/json",
|
|
2113
|
-
...
|
|
2114
|
-
? { Authorization: `Bearer ${bearerToken}` }
|
|
2115
|
-
: {}),
|
|
2108
|
+
...auth,
|
|
2116
2109
|
},
|
|
2117
2110
|
body: JSON.stringify({
|
|
2118
2111
|
conversationKey: assistantId,
|
|
@@ -2171,85 +2164,6 @@ function ChatApp({
|
|
|
2171
2164
|
return;
|
|
2172
2165
|
}
|
|
2173
2166
|
|
|
2174
|
-
if (trimmed === "/pair") {
|
|
2175
|
-
h.showSpinner("Generating pairing credentials...");
|
|
2176
|
-
|
|
2177
|
-
const isConnected = await ensureConnected();
|
|
2178
|
-
if (!isConnected) {
|
|
2179
|
-
h.hideSpinner();
|
|
2180
|
-
h.showError("Cannot pair — not connected to the assistant runtime.");
|
|
2181
|
-
return;
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
try {
|
|
2185
|
-
const pairingRequestId = randomUUID();
|
|
2186
|
-
const pairingSecret = randomBytes(32).toString("hex");
|
|
2187
|
-
const gatewayUrl = runtimeUrl;
|
|
2188
|
-
|
|
2189
|
-
// Call /pairing/register on the gateway (dedicated pairing proxy route)
|
|
2190
|
-
const registerUrl = `${runtimeUrl}/pairing/register`;
|
|
2191
|
-
const registerRes = await fetch(registerUrl, {
|
|
2192
|
-
method: "POST",
|
|
2193
|
-
headers: {
|
|
2194
|
-
"Content-Type": "application/json",
|
|
2195
|
-
...(bearerToken
|
|
2196
|
-
? { Authorization: `Bearer ${bearerToken}` }
|
|
2197
|
-
: {}),
|
|
2198
|
-
},
|
|
2199
|
-
body: JSON.stringify({
|
|
2200
|
-
pairingRequestId,
|
|
2201
|
-
pairingSecret,
|
|
2202
|
-
gatewayUrl,
|
|
2203
|
-
}),
|
|
2204
|
-
});
|
|
2205
|
-
|
|
2206
|
-
if (!registerRes.ok) {
|
|
2207
|
-
const body = await registerRes.text().catch(() => "");
|
|
2208
|
-
throw new Error(
|
|
2209
|
-
`HTTP ${registerRes.status}: ${body || registerRes.statusText}`,
|
|
2210
|
-
);
|
|
2211
|
-
}
|
|
2212
|
-
|
|
2213
|
-
let username: string;
|
|
2214
|
-
try {
|
|
2215
|
-
username = userInfo().username;
|
|
2216
|
-
} catch {
|
|
2217
|
-
username = "";
|
|
2218
|
-
}
|
|
2219
|
-
const hostId = createHash("sha256")
|
|
2220
|
-
.update(hostname() + username)
|
|
2221
|
-
.digest("hex");
|
|
2222
|
-
const payload = JSON.stringify({
|
|
2223
|
-
type: "vellum-assistant",
|
|
2224
|
-
v: 4,
|
|
2225
|
-
id: hostId,
|
|
2226
|
-
g: gatewayUrl,
|
|
2227
|
-
pairingRequestId,
|
|
2228
|
-
pairingSecret,
|
|
2229
|
-
});
|
|
2230
|
-
|
|
2231
|
-
const qrString = await new Promise<string>((resolve) => {
|
|
2232
|
-
qrcode.generate(payload, { small: true }, (code: string) => {
|
|
2233
|
-
resolve(code);
|
|
2234
|
-
});
|
|
2235
|
-
});
|
|
2236
|
-
|
|
2237
|
-
h.hideSpinner();
|
|
2238
|
-
h.addStatus(
|
|
2239
|
-
`Pairing Ready\n\n` +
|
|
2240
|
-
`Scan this QR code with the Vellum iOS app:\n\n` +
|
|
2241
|
-
`${qrString}\n` +
|
|
2242
|
-
`This pairing request expires in 5 minutes. Run /pair again to generate a new one.`,
|
|
2243
|
-
);
|
|
2244
|
-
} catch (err) {
|
|
2245
|
-
h.hideSpinner();
|
|
2246
|
-
h.showError(
|
|
2247
|
-
`Pairing failed: ${err instanceof Error ? err.message : err}`,
|
|
2248
|
-
);
|
|
2249
|
-
}
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
2167
|
if (busyRef.current) {
|
|
2254
2168
|
// /btw is already handled above this block
|
|
2255
2169
|
if (!trimmed.startsWith("/")) {
|
|
@@ -2278,7 +2192,7 @@ function ChatApp({
|
|
|
2278
2192
|
assistantId,
|
|
2279
2193
|
trimmed,
|
|
2280
2194
|
controller.signal,
|
|
2281
|
-
|
|
2195
|
+
auth,
|
|
2282
2196
|
);
|
|
2283
2197
|
clearTimeout(timeoutId);
|
|
2284
2198
|
if (sendResult.accepted) {
|
|
@@ -2329,7 +2243,7 @@ function ChatApp({
|
|
|
2329
2243
|
assistantId,
|
|
2330
2244
|
trimmed,
|
|
2331
2245
|
controller.signal,
|
|
2332
|
-
|
|
2246
|
+
auth,
|
|
2333
2247
|
);
|
|
2334
2248
|
clearTimeout(timeoutId);
|
|
2335
2249
|
if (!sendResult.accepted) {
|
|
@@ -2356,7 +2270,7 @@ function ChatApp({
|
|
|
2356
2270
|
[
|
|
2357
2271
|
runtimeUrl,
|
|
2358
2272
|
assistantId,
|
|
2359
|
-
|
|
2273
|
+
auth,
|
|
2360
2274
|
project,
|
|
2361
2275
|
zone,
|
|
2362
2276
|
cleanup,
|
|
@@ -2670,7 +2584,7 @@ export function renderChatApp(
|
|
|
2670
2584
|
assistantId: string,
|
|
2671
2585
|
species: Species,
|
|
2672
2586
|
onExit: () => void,
|
|
2673
|
-
options?: {
|
|
2587
|
+
options?: { auth?: Record<string, string>; project?: string; zone?: string },
|
|
2674
2588
|
): ChatAppInstance {
|
|
2675
2589
|
let chatHandle: ChatAppHandle | null = null;
|
|
2676
2590
|
|
|
@@ -2679,7 +2593,7 @@ export function renderChatApp(
|
|
|
2679
2593
|
runtimeUrl={runtimeUrl}
|
|
2680
2594
|
assistantId={assistantId}
|
|
2681
2595
|
species={species}
|
|
2682
|
-
|
|
2596
|
+
auth={options?.auth}
|
|
2683
2597
|
project={options?.project}
|
|
2684
2598
|
zone={options?.zone}
|
|
2685
2599
|
onExit={onExit}
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { hatch } from "./commands/hatch";
|
|
|
11
11
|
import { login, logout, whoami } from "./commands/login";
|
|
12
12
|
import { logs } from "./commands/logs";
|
|
13
13
|
import { message } from "./commands/message";
|
|
14
|
-
import { pair } from "./commands/pair";
|
|
15
14
|
import { ps } from "./commands/ps";
|
|
16
15
|
import { recover } from "./commands/recover";
|
|
17
16
|
import { restore } from "./commands/restore";
|
|
@@ -26,10 +25,7 @@ import { tunnel } from "./commands/tunnel";
|
|
|
26
25
|
import { upgrade } from "./commands/upgrade";
|
|
27
26
|
import { use } from "./commands/use";
|
|
28
27
|
import { wake } from "./commands/wake";
|
|
29
|
-
import {
|
|
30
|
-
resolveAssistant,
|
|
31
|
-
setActiveAssistant,
|
|
32
|
-
} from "./lib/assistant-config";
|
|
28
|
+
import { resolveAssistant, setActiveAssistant } from "./lib/assistant-config";
|
|
33
29
|
import { loadGuardianToken } from "./lib/guardian-token";
|
|
34
30
|
import { checkHealth } from "./lib/health-check";
|
|
35
31
|
|
|
@@ -45,7 +41,6 @@ const commands = {
|
|
|
45
41
|
logout,
|
|
46
42
|
logs,
|
|
47
43
|
message,
|
|
48
|
-
pair,
|
|
49
44
|
ps,
|
|
50
45
|
recover,
|
|
51
46
|
restore,
|
|
@@ -80,7 +75,6 @@ function printHelp(): void {
|
|
|
80
75
|
console.log(" login Log in to the Vellum platform");
|
|
81
76
|
console.log(" logout Log out of the Vellum platform");
|
|
82
77
|
console.log(" message Send a message to a running assistant");
|
|
83
|
-
console.log(" pair Pair with a remote assistant via QR code");
|
|
84
78
|
console.log(
|
|
85
79
|
" ps List assistants (or processes for a specific assistant)",
|
|
86
80
|
);
|
|
@@ -29,50 +29,41 @@ function buildAssistantArgs(
|
|
|
29
29
|
return builders.assistant();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
expect(netAdminIdx).toBeGreaterThan(0);
|
|
43
|
-
expect(args[netAdminIdx - 1]).toBe("--cap-add");
|
|
32
|
+
function buildGatewayArgs(
|
|
33
|
+
overrides: Partial<Parameters<typeof serviceDockerRunArgs>[0]> = {},
|
|
34
|
+
): string[] {
|
|
35
|
+
const res = dockerResourceNames(instanceName);
|
|
36
|
+
const builders = serviceDockerRunArgs({
|
|
37
|
+
gatewayPort: 7830,
|
|
38
|
+
imageTags,
|
|
39
|
+
instanceName,
|
|
40
|
+
res,
|
|
41
|
+
...overrides,
|
|
44
42
|
});
|
|
43
|
+
return builders.gateway();
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
describe("serviceDockerRunArgs — assistant", () => {
|
|
47
|
+
test("does not grant elevated capabilities or disable security profiles", () => {
|
|
47
48
|
const args = buildAssistantArgs();
|
|
48
|
-
|
|
49
|
-
expect(
|
|
50
|
-
expect(args
|
|
51
|
-
|
|
52
|
-
expect(
|
|
53
|
-
expect(args
|
|
49
|
+
expect(args).not.toContain("--privileged");
|
|
50
|
+
expect(args).not.toContain("--cap-add");
|
|
51
|
+
expect(args).not.toContain("SYS_ADMIN");
|
|
52
|
+
expect(args).not.toContain("NET_ADMIN");
|
|
53
|
+
expect(args).not.toContain("seccomp=unconfined");
|
|
54
|
+
expect(args).not.toContain("apparmor=unconfined");
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
test("
|
|
57
|
+
test("does not mount a dockerd data volume", () => {
|
|
57
58
|
const args = buildAssistantArgs();
|
|
58
|
-
|
|
59
|
-
const mountIndex = args.indexOf(spec);
|
|
60
|
-
expect(mountIndex).toBeGreaterThan(0);
|
|
61
|
-
expect(args[mountIndex - 1]).toBe("-v");
|
|
59
|
+
expect(args.some((a) => a.includes("/var/lib/docker"))).toBe(false);
|
|
62
60
|
});
|
|
63
61
|
|
|
64
|
-
test("does NOT bind-mount the host Docker socket
|
|
62
|
+
test("does NOT bind-mount the host Docker socket", () => {
|
|
65
63
|
const args = buildAssistantArgs();
|
|
66
64
|
expect(args).not.toContain("/var/run/docker.sock:/var/run/docker.sock");
|
|
67
65
|
});
|
|
68
66
|
|
|
69
|
-
test("does NOT set VELLUM_WORKSPACE_VOLUME_NAME (legacy Phase 1.8 hint, no longer needed in DinD)", () => {
|
|
70
|
-
const args = buildAssistantArgs();
|
|
71
|
-
expect(
|
|
72
|
-
args.some((a) => a.startsWith("VELLUM_WORKSPACE_VOLUME_NAME=")),
|
|
73
|
-
).toBe(false);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
67
|
test("keeps existing workspace and socket volume mounts intact", () => {
|
|
77
68
|
const args = buildAssistantArgs();
|
|
78
69
|
expect(args).toContain(`${instanceName}-workspace:/workspace`);
|
|
@@ -112,6 +103,33 @@ describe("serviceDockerRunArgs — assistant", () => {
|
|
|
112
103
|
});
|
|
113
104
|
});
|
|
114
105
|
|
|
106
|
+
describe("serviceDockerRunArgs — gateway", () => {
|
|
107
|
+
const savedVelayBaseUrl = process.env.VELAY_BASE_URL;
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
delete process.env.VELAY_BASE_URL;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
if (savedVelayBaseUrl === undefined) delete process.env.VELAY_BASE_URL;
|
|
115
|
+
else process.env.VELAY_BASE_URL = savedVelayBaseUrl;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("passes VELAY_BASE_URL into the gateway container when set", () => {
|
|
119
|
+
process.env.VELAY_BASE_URL = "http://host.docker.internal:8501";
|
|
120
|
+
|
|
121
|
+
expect(buildGatewayArgs()).toContain(
|
|
122
|
+
"VELAY_BASE_URL=http://host.docker.internal:8501",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("omits VELAY_BASE_URL from gateway args when unset", () => {
|
|
127
|
+
expect(
|
|
128
|
+
buildGatewayArgs().some((arg) => arg.startsWith("VELAY_BASE_URL=")),
|
|
129
|
+
).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
115
133
|
describe("VELLUM_AVATAR_DEVICE passthrough", () => {
|
|
116
134
|
const savedValue = process.env[AVATAR_DEVICE_ENV_VAR];
|
|
117
135
|
|