@vellumai/cli 0.7.1 → 0.7.3
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 +515 -10
- package/src/commands/backup.ts +35 -2
- package/src/commands/client.ts +90 -7
- package/src/commands/exec.ts +13 -4
- package/src/commands/hatch.ts +1 -1
- package/src/commands/login.ts +11 -0
- package/src/commands/restore.ts +7 -1
- package/src/commands/rollback.ts +1 -1
- package/src/commands/setup.ts +38 -73
- package/src/commands/teleport.ts +122 -12
- package/src/commands/upgrade.ts +8 -2
- package/src/commands/wake.ts +5 -16
- package/src/components/DefaultMainScreen.tsx +42 -130
- package/src/index.ts +1 -7
- package/src/lib/__tests__/docker.test.ts +53 -35
- 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/client-identity.ts +9 -5
- package/src/lib/docker.ts +6 -267
- package/src/lib/environments/paths.ts +20 -0
- 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 +100 -1
- package/src/lib/retire-local.ts +2 -2
- package/src/lib/runtime-url.ts +22 -0
- package/src/lib/statefulset.ts +375 -0
- package/src/lib/upgrade-lifecycle.ts +97 -1
- 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,
|
|
@@ -14,7 +12,7 @@ import {
|
|
|
14
12
|
import { Box, render as inkRender, Text, useInput, useStdout } from "ink";
|
|
15
13
|
|
|
16
14
|
import { removeAssistantEntry } from "../lib/assistant-config";
|
|
17
|
-
|
|
15
|
+
|
|
18
16
|
import { SPECIES_CONFIG, type Species } from "../lib/constants";
|
|
19
17
|
import { callDoctorDaemon, type ChatLogEntry } from "../lib/doctor-client";
|
|
20
18
|
import { checkHealth } from "../lib/health-check";
|
|
@@ -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,17 +348,15 @@ 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()}`;
|
|
358
|
-
|
|
359
|
-
tuiLog.info("sse connect", { url, clientHeaders });
|
|
355
|
+
tuiLog.info("sse connect", { url, authHeaders: Object.keys(auth ?? {}) });
|
|
360
356
|
const response = await fetch(url, {
|
|
361
357
|
headers: {
|
|
362
358
|
Accept: "text/event-stream",
|
|
363
|
-
...
|
|
364
|
-
...clientHeaders,
|
|
359
|
+
...auth,
|
|
365
360
|
},
|
|
366
361
|
signal,
|
|
367
362
|
});
|
|
@@ -457,7 +452,7 @@ async function handleConfirmationPrompt(
|
|
|
457
452
|
requestId: string,
|
|
458
453
|
confirmation: PendingConfirmation,
|
|
459
454
|
chatApp: ChatAppHandle,
|
|
460
|
-
|
|
455
|
+
auth?: Record<string, string>,
|
|
461
456
|
): Promise<void> {
|
|
462
457
|
const preview = formatConfirmationPreview(
|
|
463
458
|
confirmation.toolName,
|
|
@@ -483,7 +478,7 @@ async function handleConfirmationPrompt(
|
|
|
483
478
|
const index = await chatApp.showSelection("Tool Approval", options);
|
|
484
479
|
|
|
485
480
|
if (index === 0) {
|
|
486
|
-
await submitDecision(baseUrl, assistantId, requestId, "allow",
|
|
481
|
+
await submitDecision(baseUrl, assistantId, requestId, "allow", auth);
|
|
487
482
|
chatApp.addStatus("\u2714 Allowed", "green");
|
|
488
483
|
return;
|
|
489
484
|
}
|
|
@@ -495,7 +490,7 @@ async function handleConfirmationPrompt(
|
|
|
495
490
|
confirmation,
|
|
496
491
|
chatApp,
|
|
497
492
|
"always_allow",
|
|
498
|
-
|
|
493
|
+
auth,
|
|
499
494
|
);
|
|
500
495
|
return;
|
|
501
496
|
}
|
|
@@ -507,12 +502,12 @@ async function handleConfirmationPrompt(
|
|
|
507
502
|
confirmation,
|
|
508
503
|
chatApp,
|
|
509
504
|
"always_deny",
|
|
510
|
-
|
|
505
|
+
auth,
|
|
511
506
|
);
|
|
512
507
|
return;
|
|
513
508
|
}
|
|
514
509
|
|
|
515
|
-
await submitDecision(baseUrl, assistantId, requestId, "deny",
|
|
510
|
+
await submitDecision(baseUrl, assistantId, requestId, "deny", auth);
|
|
516
511
|
chatApp.addStatus("\u2718 Denied", "yellow");
|
|
517
512
|
}
|
|
518
513
|
|
|
@@ -523,7 +518,7 @@ async function handlePatternSelection(
|
|
|
523
518
|
confirmation: PendingConfirmation,
|
|
524
519
|
chatApp: ChatAppHandle,
|
|
525
520
|
trustDecision: TrustDecision,
|
|
526
|
-
|
|
521
|
+
auth?: Record<string, string>,
|
|
527
522
|
): Promise<void> {
|
|
528
523
|
const allowlistOptions = confirmation.allowlistOptions ?? [];
|
|
529
524
|
const label = trustDecision === "always_deny" ? "Denylist" : "Allowlist";
|
|
@@ -544,12 +539,12 @@ async function handlePatternSelection(
|
|
|
544
539
|
chatApp,
|
|
545
540
|
selectedPattern,
|
|
546
541
|
trustDecision,
|
|
547
|
-
|
|
542
|
+
auth,
|
|
548
543
|
);
|
|
549
544
|
return;
|
|
550
545
|
}
|
|
551
546
|
|
|
552
|
-
await submitDecision(baseUrl, assistantId, requestId, "deny",
|
|
547
|
+
await submitDecision(baseUrl, assistantId, requestId, "deny", auth);
|
|
553
548
|
chatApp.addStatus("\u2718 Denied", "yellow");
|
|
554
549
|
}
|
|
555
550
|
|
|
@@ -561,7 +556,7 @@ async function handleScopeSelection(
|
|
|
561
556
|
chatApp: ChatAppHandle,
|
|
562
557
|
selectedPattern: string,
|
|
563
558
|
trustDecision: TrustDecision,
|
|
564
|
-
|
|
559
|
+
auth?: Record<string, string>,
|
|
565
560
|
): Promise<void> {
|
|
566
561
|
const scopeOptions = confirmation.scopeOptions ?? [];
|
|
567
562
|
const label = trustDecision === "always_deny" ? "Denylist" : "Allowlist";
|
|
@@ -578,14 +573,14 @@ async function handleScopeSelection(
|
|
|
578
573
|
selectedPattern,
|
|
579
574
|
scopeOptions[index].scope,
|
|
580
575
|
ruleDecision,
|
|
581
|
-
|
|
576
|
+
auth,
|
|
582
577
|
);
|
|
583
578
|
await submitDecision(
|
|
584
579
|
baseUrl,
|
|
585
580
|
assistantId,
|
|
586
581
|
requestId,
|
|
587
582
|
ruleDecision === "deny" ? "deny" : "allow",
|
|
588
|
-
|
|
583
|
+
auth,
|
|
589
584
|
);
|
|
590
585
|
const ruleLabel =
|
|
591
586
|
trustDecision === "always_deny" ? "Denylisted" : "Allowlisted";
|
|
@@ -597,7 +592,7 @@ async function handleScopeSelection(
|
|
|
597
592
|
return;
|
|
598
593
|
}
|
|
599
594
|
|
|
600
|
-
await submitDecision(baseUrl, assistantId, requestId, "deny",
|
|
595
|
+
await submitDecision(baseUrl, assistantId, requestId, "deny", auth);
|
|
601
596
|
chatApp.addStatus("\u2718 Denied", "yellow");
|
|
602
597
|
}
|
|
603
598
|
|
|
@@ -753,10 +748,6 @@ function HelpDisplay(): ReactElement {
|
|
|
753
748
|
{" /clear "}
|
|
754
749
|
<Text dimColor>Clear the screen</Text>
|
|
755
750
|
</Text>
|
|
756
|
-
<Text>
|
|
757
|
-
{" /pair "}
|
|
758
|
-
<Text dimColor>Generate a QR code for mobile device pairing</Text>
|
|
759
|
-
</Text>
|
|
760
751
|
<Text>
|
|
761
752
|
{" /help, ? "}
|
|
762
753
|
<Text dimColor>Show this help</Text>
|
|
@@ -1368,7 +1359,9 @@ interface ChatAppProps {
|
|
|
1368
1359
|
runtimeUrl: string;
|
|
1369
1360
|
assistantId: string;
|
|
1370
1361
|
species: Species;
|
|
1371
|
-
|
|
1362
|
+
/** Pre-built auth headers (e.g. { Authorization: "Bearer ..." } for local,
|
|
1363
|
+
* { "X-Session-Token": "...", "Vellum-Organization-Id": "..." } for platform). */
|
|
1364
|
+
auth?: Record<string, string>;
|
|
1372
1365
|
project?: string;
|
|
1373
1366
|
zone?: string;
|
|
1374
1367
|
onExit: () => void;
|
|
@@ -1379,7 +1372,7 @@ function ChatApp({
|
|
|
1379
1372
|
runtimeUrl,
|
|
1380
1373
|
assistantId,
|
|
1381
1374
|
species,
|
|
1382
|
-
|
|
1375
|
+
auth,
|
|
1383
1376
|
project,
|
|
1384
1377
|
zone,
|
|
1385
1378
|
onExit,
|
|
@@ -1692,7 +1685,7 @@ function ChatApp({
|
|
|
1692
1685
|
const historyResponse = await pollMessages(
|
|
1693
1686
|
runtimeUrl,
|
|
1694
1687
|
assistantId,
|
|
1695
|
-
|
|
1688
|
+
auth,
|
|
1696
1689
|
);
|
|
1697
1690
|
h.hideSpinner();
|
|
1698
1691
|
if (historyResponse.messages.length > 0) {
|
|
@@ -1717,7 +1710,7 @@ function ChatApp({
|
|
|
1717
1710
|
assistantId,
|
|
1718
1711
|
assistantId,
|
|
1719
1712
|
sseAc.signal,
|
|
1720
|
-
|
|
1713
|
+
auth,
|
|
1721
1714
|
)) {
|
|
1722
1715
|
const hRef = handleRef_.current;
|
|
1723
1716
|
if (!hRef) continue;
|
|
@@ -1788,7 +1781,7 @@ function ChatApp({
|
|
|
1788
1781
|
event.persistentDecisionsAllowed,
|
|
1789
1782
|
},
|
|
1790
1783
|
hRef,
|
|
1791
|
-
|
|
1784
|
+
auth,
|
|
1792
1785
|
);
|
|
1793
1786
|
hRef.showSpinner("Working...");
|
|
1794
1787
|
break;
|
|
@@ -1819,7 +1812,7 @@ function ChatApp({
|
|
|
1819
1812
|
delivery,
|
|
1820
1813
|
}),
|
|
1821
1814
|
},
|
|
1822
|
-
|
|
1815
|
+
auth,
|
|
1823
1816
|
);
|
|
1824
1817
|
},
|
|
1825
1818
|
);
|
|
@@ -1903,7 +1896,7 @@ function ChatApp({
|
|
|
1903
1896
|
);
|
|
1904
1897
|
return false;
|
|
1905
1898
|
}
|
|
1906
|
-
}, [runtimeUrl, assistantId,
|
|
1899
|
+
}, [runtimeUrl, assistantId, auth]);
|
|
1907
1900
|
|
|
1908
1901
|
const handleInput = useCallback(
|
|
1909
1902
|
async (input: string): Promise<void> => {
|
|
@@ -2110,9 +2103,7 @@ function ChatApp({
|
|
|
2110
2103
|
method: "POST",
|
|
2111
2104
|
headers: {
|
|
2112
2105
|
"Content-Type": "application/json",
|
|
2113
|
-
...
|
|
2114
|
-
? { Authorization: `Bearer ${bearerToken}` }
|
|
2115
|
-
: {}),
|
|
2106
|
+
...auth,
|
|
2116
2107
|
},
|
|
2117
2108
|
body: JSON.stringify({
|
|
2118
2109
|
conversationKey: assistantId,
|
|
@@ -2171,85 +2162,6 @@ function ChatApp({
|
|
|
2171
2162
|
return;
|
|
2172
2163
|
}
|
|
2173
2164
|
|
|
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
2165
|
if (busyRef.current) {
|
|
2254
2166
|
// /btw is already handled above this block
|
|
2255
2167
|
if (!trimmed.startsWith("/")) {
|
|
@@ -2278,7 +2190,7 @@ function ChatApp({
|
|
|
2278
2190
|
assistantId,
|
|
2279
2191
|
trimmed,
|
|
2280
2192
|
controller.signal,
|
|
2281
|
-
|
|
2193
|
+
auth,
|
|
2282
2194
|
);
|
|
2283
2195
|
clearTimeout(timeoutId);
|
|
2284
2196
|
if (sendResult.accepted) {
|
|
@@ -2329,7 +2241,7 @@ function ChatApp({
|
|
|
2329
2241
|
assistantId,
|
|
2330
2242
|
trimmed,
|
|
2331
2243
|
controller.signal,
|
|
2332
|
-
|
|
2244
|
+
auth,
|
|
2333
2245
|
);
|
|
2334
2246
|
clearTimeout(timeoutId);
|
|
2335
2247
|
if (!sendResult.accepted) {
|
|
@@ -2356,7 +2268,7 @@ function ChatApp({
|
|
|
2356
2268
|
[
|
|
2357
2269
|
runtimeUrl,
|
|
2358
2270
|
assistantId,
|
|
2359
|
-
|
|
2271
|
+
auth,
|
|
2360
2272
|
project,
|
|
2361
2273
|
zone,
|
|
2362
2274
|
cleanup,
|
|
@@ -2670,7 +2582,7 @@ export function renderChatApp(
|
|
|
2670
2582
|
assistantId: string,
|
|
2671
2583
|
species: Species,
|
|
2672
2584
|
onExit: () => void,
|
|
2673
|
-
options?: {
|
|
2585
|
+
options?: { auth?: Record<string, string>; project?: string; zone?: string },
|
|
2674
2586
|
): ChatAppInstance {
|
|
2675
2587
|
let chatHandle: ChatAppHandle | null = null;
|
|
2676
2588
|
|
|
@@ -2679,7 +2591,7 @@ export function renderChatApp(
|
|
|
2679
2591
|
runtimeUrl={runtimeUrl}
|
|
2680
2592
|
assistantId={assistantId}
|
|
2681
2593
|
species={species}
|
|
2682
|
-
|
|
2594
|
+
auth={options?.auth}
|
|
2683
2595
|
project={options?.project}
|
|
2684
2596
|
zone={options?.zone}
|
|
2685
2597
|
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
|
);
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
AVATAR_DEVICE_ENV_VAR,
|
|
5
5
|
dockerResourceNames,
|
|
6
6
|
resolveAvatarDevicePath,
|
|
7
|
-
serviceDockerRunArgs,
|
|
8
7
|
type ServiceName,
|
|
9
8
|
} from "../docker.js";
|
|
9
|
+
import { buildServiceRunArgs } from "../statefulset.js";
|
|
10
10
|
|
|
11
11
|
const instanceName = "test-instance";
|
|
12
12
|
const imageTags: Record<ServiceName, string> = {
|
|
@@ -16,10 +16,10 @@ const imageTags: Record<ServiceName, string> = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
function buildAssistantArgs(
|
|
19
|
-
overrides: Partial<Parameters<typeof
|
|
19
|
+
overrides: Partial<Parameters<typeof buildServiceRunArgs>[0]> = {},
|
|
20
20
|
): string[] {
|
|
21
21
|
const res = dockerResourceNames(instanceName);
|
|
22
|
-
const builders =
|
|
22
|
+
const builders = buildServiceRunArgs({
|
|
23
23
|
gatewayPort: 7830,
|
|
24
24
|
imageTags,
|
|
25
25
|
instanceName,
|
|
@@ -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 buildServiceRunArgs>[0]> = {},
|
|
34
|
+
): string[] {
|
|
35
|
+
const res = dockerResourceNames(instanceName);
|
|
36
|
+
const builders = buildServiceRunArgs({
|
|
37
|
+
gatewayPort: 7830,
|
|
38
|
+
imageTags,
|
|
39
|
+
instanceName,
|
|
40
|
+
res,
|
|
41
|
+
...overrides,
|
|
44
42
|
});
|
|
43
|
+
return builders.gateway();
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
describe("buildServiceRunArgs — 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("buildServiceRunArgs — 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
|
|