@vellumai/cli 0.7.2 → 0.8.0
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/package.json +1 -1
- package/src/__tests__/teleport.test.ts +3 -0
- package/src/commands/client.ts +47 -5
- package/src/commands/events.ts +0 -3
- package/src/commands/login.ts +11 -0
- package/src/commands/rollback.ts +1 -1
- package/src/commands/teleport.ts +11 -1
- package/src/commands/upgrade.ts +2 -2
- package/src/components/DefaultMainScreen.tsx +2 -4
- package/src/lib/__tests__/docker.test.ts +7 -7
- package/src/lib/client-identity.ts +9 -5
- package/src/lib/docker.ts +6 -28
- package/src/lib/environments/paths.ts +20 -0
- package/src/lib/local.ts +37 -0
- package/src/lib/platform-client.ts +4 -0
- package/src/lib/{docker-statefulset.ts → statefulset.ts} +8 -14
- package/src/lib/upgrade-lifecycle.ts +37 -6
package/package.json
CHANGED
|
@@ -2201,6 +2201,9 @@ describe("platform credential injection", () => {
|
|
|
2201
2201
|
"device-id-123",
|
|
2202
2202
|
"my-local",
|
|
2203
2203
|
"cli",
|
|
2204
|
+
undefined, // assistantVersion (gateway unreachable in test)
|
|
2205
|
+
expect.any(String), // platformUrl from getPlatformUrl()
|
|
2206
|
+
undefined, // ingressUrl (gateway unreachable in test)
|
|
2204
2207
|
);
|
|
2205
2208
|
expect(injectCredentialsIntoAssistantMock).toHaveBeenCalledWith({
|
|
2206
2209
|
gatewayUrl: "http://localhost:7821",
|
package/src/commands/client.ts
CHANGED
|
@@ -12,12 +12,19 @@ import {
|
|
|
12
12
|
} from "../lib/constants";
|
|
13
13
|
import { loadGuardianToken } from "../lib/guardian-token";
|
|
14
14
|
import { getLocalLanIPv4 } from "../lib/local";
|
|
15
|
+
import {
|
|
16
|
+
CLI_INTERFACE_ID,
|
|
17
|
+
getClientRegistrationHeaders,
|
|
18
|
+
} from "../lib/client-identity";
|
|
15
19
|
import {
|
|
16
20
|
fetchOrganizationId,
|
|
17
21
|
readPlatformToken,
|
|
18
22
|
} from "../lib/platform-client";
|
|
19
23
|
import { tuiLog } from "../lib/tui-log";
|
|
20
24
|
|
|
25
|
+
const SUPPORTED_INTERFACES = ["cli"] as const;
|
|
26
|
+
type SupportedInterface = (typeof SUPPORTED_INTERFACES)[number];
|
|
27
|
+
|
|
21
28
|
const ANSI = {
|
|
22
29
|
reset: "\x1b[0m",
|
|
23
30
|
bold: "\x1b[1m",
|
|
@@ -36,6 +43,8 @@ interface ParsedArgs {
|
|
|
36
43
|
platformToken?: string;
|
|
37
44
|
/** Guardian JWT (Authorization: Bearer), set for local assistants. */
|
|
38
45
|
bearerToken?: string;
|
|
46
|
+
/** Interface identifier sent as X-Vellum-Interface-Id on all requests. */
|
|
47
|
+
interfaceId: SupportedInterface;
|
|
39
48
|
project?: string;
|
|
40
49
|
zone?: string;
|
|
41
50
|
}
|
|
@@ -54,7 +63,9 @@ function parseArgs(): ParsedArgs {
|
|
|
54
63
|
(arg === "--url" ||
|
|
55
64
|
arg === "-u" ||
|
|
56
65
|
arg === "--assistant-id" ||
|
|
57
|
-
arg === "-a"
|
|
66
|
+
arg === "-a" ||
|
|
67
|
+
arg === "--interface" ||
|
|
68
|
+
arg === "-i") &&
|
|
58
69
|
args[i + 1]
|
|
59
70
|
) {
|
|
60
71
|
flagArgs.push(arg, args[++i]);
|
|
@@ -109,6 +120,8 @@ function parseArgs(): ParsedArgs {
|
|
|
109
120
|
? undefined
|
|
110
121
|
: (loadGuardianToken(entry?.assistantId ?? "")?.accessToken ?? undefined);
|
|
111
122
|
|
|
123
|
+
let interfaceId: SupportedInterface = CLI_INTERFACE_ID;
|
|
124
|
+
|
|
112
125
|
for (let i = 0; i < flagArgs.length; i++) {
|
|
113
126
|
const flag = flagArgs[i];
|
|
114
127
|
if ((flag === "--url" || flag === "-u") && flagArgs[i + 1]) {
|
|
@@ -118,6 +131,21 @@ function parseArgs(): ParsedArgs {
|
|
|
118
131
|
flagArgs[i + 1]
|
|
119
132
|
) {
|
|
120
133
|
assistantId = flagArgs[++i];
|
|
134
|
+
} else if ((flag === "--interface" || flag === "-i") && flagArgs[i + 1]) {
|
|
135
|
+
const value = flagArgs[++i];
|
|
136
|
+
if (value === "web") {
|
|
137
|
+
console.error(
|
|
138
|
+
`--interface web is not yet supported. Coming soon.`,
|
|
139
|
+
);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
if (!(SUPPORTED_INTERFACES as readonly string[]).includes(value)) {
|
|
143
|
+
console.error(
|
|
144
|
+
`Unknown interface '${value}'. Supported: ${SUPPORTED_INTERFACES.join(", ")}.`,
|
|
145
|
+
);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
interfaceId = value as SupportedInterface;
|
|
121
149
|
}
|
|
122
150
|
}
|
|
123
151
|
|
|
@@ -128,6 +156,7 @@ function parseArgs(): ParsedArgs {
|
|
|
128
156
|
cloud,
|
|
129
157
|
platformToken,
|
|
130
158
|
bearerToken,
|
|
159
|
+
interfaceId,
|
|
131
160
|
project: entry?.project,
|
|
132
161
|
zone: entry?.zone,
|
|
133
162
|
};
|
|
@@ -184,6 +213,7 @@ ${ANSI.bold}ARGUMENTS:${ANSI.reset}
|
|
|
184
213
|
${ANSI.bold}OPTIONS:${ANSI.reset}
|
|
185
214
|
-u, --url <url> Runtime URL
|
|
186
215
|
-a, --assistant-id <id> Assistant ID
|
|
216
|
+
-i, --interface <id> Interface identifier (default: cli)
|
|
187
217
|
-h, --help Show this help message
|
|
188
218
|
|
|
189
219
|
${ANSI.bold}DEFAULTS:${ANSI.reset}
|
|
@@ -206,14 +236,22 @@ export async function client(): Promise<void> {
|
|
|
206
236
|
cloud,
|
|
207
237
|
platformToken,
|
|
208
238
|
bearerToken,
|
|
239
|
+
interfaceId,
|
|
209
240
|
project,
|
|
210
241
|
zone,
|
|
211
242
|
} = parseArgs();
|
|
212
243
|
|
|
213
244
|
tuiLog.init();
|
|
214
|
-
tuiLog.info("session start", {
|
|
245
|
+
tuiLog.info("session start", {
|
|
246
|
+
runtimeUrl,
|
|
247
|
+
assistantId,
|
|
248
|
+
species,
|
|
249
|
+
cloud,
|
|
250
|
+
interfaceId,
|
|
251
|
+
});
|
|
215
252
|
|
|
216
|
-
// Build pre-constructed
|
|
253
|
+
// Build pre-constructed request headers merged from auth + client registration.
|
|
254
|
+
// Spreading into every fetch site ensures consistency across REST and SSE endpoints.
|
|
217
255
|
let auth: Record<string, string> | undefined;
|
|
218
256
|
if (cloud === "vellum" && platformToken) {
|
|
219
257
|
const orgId = await fetchOrganizationId(platformToken).catch((err) => {
|
|
@@ -223,9 +261,13 @@ export async function client(): Promise<void> {
|
|
|
223
261
|
auth = {
|
|
224
262
|
"X-Session-Token": platformToken,
|
|
225
263
|
...(orgId ? { "Vellum-Organization-Id": orgId } : {}),
|
|
264
|
+
...getClientRegistrationHeaders(interfaceId),
|
|
265
|
+
};
|
|
266
|
+
} else {
|
|
267
|
+
auth = {
|
|
268
|
+
...(bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}),
|
|
269
|
+
...getClientRegistrationHeaders(interfaceId),
|
|
226
270
|
};
|
|
227
|
-
} else if (bearerToken) {
|
|
228
|
-
auth = { Authorization: `Bearer ${bearerToken}` };
|
|
229
271
|
}
|
|
230
272
|
|
|
231
273
|
const { renderChatApp } = await import("../components/DefaultMainScreen");
|
package/src/commands/events.ts
CHANGED
package/src/commands/login.ts
CHANGED
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
setActiveAssistant,
|
|
11
11
|
} from "../lib/assistant-config";
|
|
12
12
|
import { computeDeviceId } from "../lib/guardian-token";
|
|
13
|
+
import {
|
|
14
|
+
fetchAssistantIngressUrl,
|
|
15
|
+
fetchCurrentVersion,
|
|
16
|
+
} from "../lib/upgrade-lifecycle.js";
|
|
13
17
|
import {
|
|
14
18
|
clearPlatformToken,
|
|
15
19
|
ensureSelfHostedLocalRegistration,
|
|
@@ -210,12 +214,19 @@ export async function login(): Promise<void> {
|
|
|
210
214
|
if (entry && entry.cloud !== "vellum") {
|
|
211
215
|
const orgId = await fetchOrganizationId(token);
|
|
212
216
|
const clientInstallationId = computeDeviceId();
|
|
217
|
+
const [assistantVersion, ingressUrl] = await Promise.all([
|
|
218
|
+
fetchCurrentVersion(entry.runtimeUrl),
|
|
219
|
+
fetchAssistantIngressUrl(entry.runtimeUrl, entry.bearerToken),
|
|
220
|
+
]);
|
|
213
221
|
const registration = await ensureSelfHostedLocalRegistration(
|
|
214
222
|
token,
|
|
215
223
|
orgId,
|
|
216
224
|
clientInstallationId,
|
|
217
225
|
entry.assistantId,
|
|
218
226
|
"cli",
|
|
227
|
+
assistantVersion,
|
|
228
|
+
getPlatformUrl(),
|
|
229
|
+
ingressUrl,
|
|
219
230
|
);
|
|
220
231
|
console.log(
|
|
221
232
|
`Registered assistant: ${registration.assistant.name} (${registration.assistant.id})`,
|
package/src/commands/rollback.ts
CHANGED
|
@@ -340,7 +340,7 @@ export async function rollback(): Promise<void> {
|
|
|
340
340
|
const signingKey =
|
|
341
341
|
capturedEnv["ACTOR_TOKEN_SIGNING_KEY"] || randomBytes(32).toString("hex");
|
|
342
342
|
|
|
343
|
-
// Build extra env vars, excluding keys managed by
|
|
343
|
+
// Build extra env vars, excluding keys managed by buildServiceRunArgs
|
|
344
344
|
const envKeysSetByRunArgs = new Set(CONTAINER_ENV_EXCLUDE_KEYS);
|
|
345
345
|
for (const envVar of ["ANTHROPIC_API_KEY", "VELLUM_PLATFORM_URL"]) {
|
|
346
346
|
if (process.env[envVar]) {
|
package/src/commands/teleport.ts
CHANGED
|
@@ -47,7 +47,10 @@ import { hatchLocal } from "../lib/hatch-local.js";
|
|
|
47
47
|
import { retireLocal } from "../lib/retire-local.js";
|
|
48
48
|
import { validateAssistantName } from "../lib/retire-archive.js";
|
|
49
49
|
import { stopProcessByPidFile } from "../lib/process.js";
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
fetchAssistantIngressUrl,
|
|
52
|
+
fetchCurrentVersion,
|
|
53
|
+
} from "../lib/upgrade-lifecycle.js";
|
|
51
54
|
import { compareVersions } from "../lib/version-compat.js";
|
|
52
55
|
import { join } from "node:path";
|
|
53
56
|
|
|
@@ -1066,12 +1069,19 @@ async function tryInjectPlatformCredentials(
|
|
|
1066
1069
|
const user = await fetchCurrentUser(token);
|
|
1067
1070
|
const orgId = await fetchOrganizationId(token);
|
|
1068
1071
|
const clientInstallationId = computeDeviceId();
|
|
1072
|
+
const [assistantVersion, ingressUrl] = await Promise.all([
|
|
1073
|
+
fetchCurrentVersion(entry.runtimeUrl),
|
|
1074
|
+
fetchAssistantIngressUrl(entry.runtimeUrl, entry.bearerToken),
|
|
1075
|
+
]);
|
|
1069
1076
|
const registration = await ensureSelfHostedLocalRegistration(
|
|
1070
1077
|
token,
|
|
1071
1078
|
orgId,
|
|
1072
1079
|
clientInstallationId,
|
|
1073
1080
|
entry.assistantId,
|
|
1074
1081
|
"cli",
|
|
1082
|
+
assistantVersion,
|
|
1083
|
+
getPlatformUrl(),
|
|
1084
|
+
ingressUrl,
|
|
1075
1085
|
);
|
|
1076
1086
|
|
|
1077
1087
|
// Resolve the API key: 1) fresh from registration, 2) existing from
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -429,9 +429,9 @@ async function upgradeDocker(
|
|
|
429
429
|
|
|
430
430
|
// Build the set of extra env vars to replay on the new assistant container.
|
|
431
431
|
// Captured env vars serve as the base; keys already managed by
|
|
432
|
-
//
|
|
432
|
+
// buildServiceRunArgs are excluded to avoid duplicates.
|
|
433
433
|
const envKeysSetByRunArgs = new Set(CONTAINER_ENV_EXCLUDE_KEYS);
|
|
434
|
-
// Only exclude keys that
|
|
434
|
+
// Only exclude keys that buildServiceRunArgs will actually set
|
|
435
435
|
for (const envVar of ["ANTHROPIC_API_KEY", "VELLUM_PLATFORM_URL"]) {
|
|
436
436
|
if (process.env[envVar]) {
|
|
437
437
|
envKeysSetByRunArgs.add(envVar);
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { Box, render as inkRender, Text, useInput, useStdout } from "ink";
|
|
13
13
|
|
|
14
14
|
import { removeAssistantEntry } from "../lib/assistant-config";
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
import { SPECIES_CONFIG, type Species } from "../lib/constants";
|
|
17
17
|
import { callDoctorDaemon, type ChatLogEntry } from "../lib/doctor-client";
|
|
18
18
|
import { checkHealth } from "../lib/health-check";
|
|
@@ -352,13 +352,11 @@ async function* streamEvents(
|
|
|
352
352
|
): AsyncGenerator<SseEvent> {
|
|
353
353
|
const params = new URLSearchParams({ conversationKey });
|
|
354
354
|
const url = `${baseUrl}/v1/assistants/${assistantId}/events?${params.toString()}`;
|
|
355
|
-
|
|
356
|
-
tuiLog.info("sse connect", { url, clientHeaders });
|
|
355
|
+
tuiLog.info("sse connect", { url, authHeaders: Object.keys(auth ?? {}) });
|
|
357
356
|
const response = await fetch(url, {
|
|
358
357
|
headers: {
|
|
359
358
|
Accept: "text/event-stream",
|
|
360
359
|
...auth,
|
|
361
|
-
...clientHeaders,
|
|
362
360
|
},
|
|
363
361
|
signal,
|
|
364
362
|
});
|
|
@@ -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,
|
|
@@ -30,10 +30,10 @@ function buildAssistantArgs(
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function buildGatewayArgs(
|
|
33
|
-
overrides: Partial<Parameters<typeof
|
|
33
|
+
overrides: Partial<Parameters<typeof buildServiceRunArgs>[0]> = {},
|
|
34
34
|
): string[] {
|
|
35
35
|
const res = dockerResourceNames(instanceName);
|
|
36
|
-
const builders =
|
|
36
|
+
const builders = buildServiceRunArgs({
|
|
37
37
|
gatewayPort: 7830,
|
|
38
38
|
imageTags,
|
|
39
39
|
instanceName,
|
|
@@ -43,7 +43,7 @@ function buildGatewayArgs(
|
|
|
43
43
|
return builders.gateway();
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
describe("
|
|
46
|
+
describe("buildServiceRunArgs — assistant", () => {
|
|
47
47
|
test("does not grant elevated capabilities or disable security profiles", () => {
|
|
48
48
|
const args = buildAssistantArgs();
|
|
49
49
|
expect(args).not.toContain("--privileged");
|
|
@@ -103,7 +103,7 @@ describe("serviceDockerRunArgs — assistant", () => {
|
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
describe("
|
|
106
|
+
describe("buildServiceRunArgs — gateway", () => {
|
|
107
107
|
const savedVelayBaseUrl = process.env.VELAY_BASE_URL;
|
|
108
108
|
|
|
109
109
|
beforeEach(() => {
|
|
@@ -11,7 +11,7 @@ import { randomUUID } from "crypto";
|
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import { join } from "path";
|
|
13
13
|
|
|
14
|
-
const CLI_INTERFACE_ID = "cli";
|
|
14
|
+
export const CLI_INTERFACE_ID = "cli";
|
|
15
15
|
|
|
16
16
|
let cached: string | null = null;
|
|
17
17
|
|
|
@@ -56,12 +56,16 @@ export function getClientId(): string {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Headers that identify this CLI client to the assistant daemon.
|
|
59
|
-
* Attach to
|
|
60
|
-
*
|
|
59
|
+
* Attach to all requests so the ClientRegistry can track connected
|
|
60
|
+
* clients and their capabilities.
|
|
61
|
+
*
|
|
62
|
+
* @param interfaceId - Override the interface ID (default: "cli").
|
|
61
63
|
*/
|
|
62
|
-
export function getClientRegistrationHeaders(
|
|
64
|
+
export function getClientRegistrationHeaders(
|
|
65
|
+
interfaceId: string = CLI_INTERFACE_ID,
|
|
66
|
+
): Record<string, string> {
|
|
63
67
|
return {
|
|
64
68
|
"X-Vellum-Client-Id": getClientId(),
|
|
65
|
-
"X-Vellum-Interface-Id":
|
|
69
|
+
"X-Vellum-Interface-Id": interfaceId,
|
|
66
70
|
};
|
|
67
71
|
}
|
package/src/lib/docker.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./assistant-config";
|
|
14
14
|
import type { AssistantEntry } from "./assistant-config";
|
|
15
15
|
import { writeInitialConfig } from "./config-utils";
|
|
16
|
-
import { buildServiceRunArgs } from "./
|
|
16
|
+
import { buildServiceRunArgs } from "./statefulset.js";
|
|
17
17
|
import type { Species } from "./constants";
|
|
18
18
|
import { getDefaultPorts } from "./environments/paths.js";
|
|
19
19
|
import { getCurrentEnvironment } from "./environments/resolve.js";
|
|
@@ -39,9 +39,8 @@ export const DOCKERHUB_IMAGES: Record<ServiceName, string> = {
|
|
|
39
39
|
gateway: `${DOCKERHUB_ORG}/vellum-gateway`,
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
/** Internal ports exposed by each service's Dockerfile. */
|
|
43
|
-
export
|
|
44
|
-
export const GATEWAY_INTERNAL_PORT = 7830;
|
|
42
|
+
/** Internal ports exposed by each service's Dockerfile. Re-exported from environments/paths.ts. */
|
|
43
|
+
export { ASSISTANT_INTERNAL_PORT, GATEWAY_INTERNAL_PORT } from "./environments/paths.js";
|
|
45
44
|
|
|
46
45
|
/** Max time to wait for the assistant container to emit the readiness sentinel. */
|
|
47
46
|
export const DOCKER_READY_TIMEOUT_MS = 3 * 60 * 1000;
|
|
@@ -560,28 +559,6 @@ async function buildAllImages(
|
|
|
560
559
|
);
|
|
561
560
|
}
|
|
562
561
|
|
|
563
|
-
/**
|
|
564
|
-
* Build `docker run` argument arrays for each service in the StatefulSet.
|
|
565
|
-
*
|
|
566
|
-
* Delegates to `buildServiceRunArgs` from `docker-statefulset.ts`, which owns
|
|
567
|
-
* the declarative container / volume / env spec. Signature preserved for
|
|
568
|
-
* backward compatibility with callers throughout this file.
|
|
569
|
-
*/
|
|
570
|
-
export function serviceDockerRunArgs(opts: {
|
|
571
|
-
signingKey?: string;
|
|
572
|
-
bootstrapSecret?: string;
|
|
573
|
-
cesServiceToken?: string;
|
|
574
|
-
extraAssistantEnv?: Record<string, string>;
|
|
575
|
-
gatewayPort: number;
|
|
576
|
-
imageTags: Record<ServiceName, string>;
|
|
577
|
-
defaultWorkspaceConfigPath?: string;
|
|
578
|
-
instanceName: string;
|
|
579
|
-
res: ReturnType<typeof dockerResourceNames>;
|
|
580
|
-
}): Record<ServiceName, () => string[]> {
|
|
581
|
-
const avatarDevice = resolveAvatarDevicePath();
|
|
582
|
-
return buildServiceRunArgs({ ...opts, avatarDevicePath: avatarDevice });
|
|
583
|
-
}
|
|
584
|
-
|
|
585
562
|
/** The order in which services must be started. */
|
|
586
563
|
export const SERVICE_START_ORDER: ServiceName[] = [
|
|
587
564
|
"assistant",
|
|
@@ -604,7 +581,7 @@ export async function startContainers(
|
|
|
604
581
|
},
|
|
605
582
|
log: (msg: string) => void,
|
|
606
583
|
): Promise<void> {
|
|
607
|
-
const runArgs =
|
|
584
|
+
const runArgs = buildServiceRunArgs({ ...opts, avatarDevicePath: resolveAvatarDevicePath() });
|
|
608
585
|
for (const service of SERVICE_START_ORDER) {
|
|
609
586
|
log(`🚀 Starting ${service} container...`);
|
|
610
587
|
await exec("docker", runArgs[service]());
|
|
@@ -782,7 +759,7 @@ function startFileWatcher(opts: {
|
|
|
782
759
|
let rebuilding = false;
|
|
783
760
|
|
|
784
761
|
const configs = serviceImageConfigs(repoRoot, imageTags);
|
|
785
|
-
const runArgs =
|
|
762
|
+
const runArgs = buildServiceRunArgs({
|
|
786
763
|
signingKey: opts.signingKey,
|
|
787
764
|
bootstrapSecret: opts.bootstrapSecret,
|
|
788
765
|
cesServiceToken: opts.cesServiceToken,
|
|
@@ -790,6 +767,7 @@ function startFileWatcher(opts: {
|
|
|
790
767
|
imageTags,
|
|
791
768
|
instanceName,
|
|
792
769
|
res,
|
|
770
|
+
avatarDevicePath: resolveAvatarDevicePath(),
|
|
793
771
|
});
|
|
794
772
|
const containerForService: Record<ServiceName, string> = {
|
|
795
773
|
assistant: res.assistantContainer,
|
|
@@ -98,6 +98,26 @@ export function getDefaultPorts(env: EnvironmentDefinition): PortMap {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Runtime state directory for an environment (upgrade logs, etc.).
|
|
103
|
+
* Production uses `~/.local/share/vellum/`; non-production environments
|
|
104
|
+
* use `~/.local/share/vellum-<env>/`.
|
|
105
|
+
*/
|
|
106
|
+
export function getStateDir(env: EnvironmentDefinition): string {
|
|
107
|
+
if (env.name === PRODUCTION_ENVIRONMENT_NAME) {
|
|
108
|
+
return join(xdgDataHome(), "vellum");
|
|
109
|
+
}
|
|
110
|
+
return join(xdgDataHome(), `vellum-${env.name}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Named port constants derived from `DEFAULT_PORTS`.
|
|
115
|
+
* These are the ports the assistant and gateway services bind to *inside*
|
|
116
|
+
* their container (or process). They are stable across environments.
|
|
117
|
+
*/
|
|
118
|
+
export const ASSISTANT_INTERNAL_PORT = DEFAULT_PORTS.daemon;
|
|
119
|
+
export const GATEWAY_INTERNAL_PORT = DEFAULT_PORTS.gateway;
|
|
120
|
+
|
|
101
121
|
function xdgDataHome(): string {
|
|
102
122
|
return (
|
|
103
123
|
process.env.XDG_DATA_HOME?.trim() || join(homedir(), ".local", "share")
|
package/src/lib/local.ts
CHANGED
|
@@ -840,6 +840,42 @@ export function isGatewayWatchModeAvailable(): boolean {
|
|
|
840
840
|
}
|
|
841
841
|
}
|
|
842
842
|
|
|
843
|
+
/**
|
|
844
|
+
* Write (or overwrite) a shell wrapper at `<workspace>/bin/assistant` that
|
|
845
|
+
* pre-injects the three instance-specific env vars before exec-ing the real
|
|
846
|
+
* assistant binary from the app bundle.
|
|
847
|
+
*
|
|
848
|
+
* This lets developers invoke `<workspace>/bin/assistant <command>` directly
|
|
849
|
+
* from the terminal without manually setting env vars. Only created when a
|
|
850
|
+
* compiled `assistant` binary is present adjacent to the CLI executable (i.e.
|
|
851
|
+
* inside a desktop app bundle) — a no-op in source/watch mode.
|
|
852
|
+
*
|
|
853
|
+
* The wrapper is idempotent: safe to call on every daemon wake.
|
|
854
|
+
*/
|
|
855
|
+
function writeAssistantWrapper(resources: LocalInstanceResources): void {
|
|
856
|
+
const assistantBinary = join(dirname(process.execPath), "assistant");
|
|
857
|
+
if (!existsSync(assistantBinary)) return;
|
|
858
|
+
|
|
859
|
+
const workspaceDir = join(resources.instanceDir, ".vellum", "workspace");
|
|
860
|
+
const protectedDir = join(resources.instanceDir, ".vellum", "protected");
|
|
861
|
+
const binDir = join(workspaceDir, "bin");
|
|
862
|
+
|
|
863
|
+
mkdirSync(binDir, { recursive: true });
|
|
864
|
+
const wrapperPath = join(binDir, "assistant");
|
|
865
|
+
writeFileSync(
|
|
866
|
+
wrapperPath,
|
|
867
|
+
[
|
|
868
|
+
"#!/bin/sh",
|
|
869
|
+
`export VELLUM_WORKSPACE_DIR="${workspaceDir}"`,
|
|
870
|
+
`export CREDENTIAL_SECURITY_DIR="${protectedDir}"`,
|
|
871
|
+
`export GATEWAY_SECURITY_DIR="${protectedDir}"`,
|
|
872
|
+
`exec "${assistantBinary}" "$@"`,
|
|
873
|
+
"",
|
|
874
|
+
].join("\n"),
|
|
875
|
+
{ mode: 0o755 },
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
843
879
|
// NOTE: startLocalDaemon() is the CLI-side daemon lifecycle manager.
|
|
844
880
|
// It should eventually converge with
|
|
845
881
|
// assistant/src/daemon/daemon-control.ts::startDaemon which is the
|
|
@@ -850,6 +886,7 @@ export async function startLocalDaemon(
|
|
|
850
886
|
options?: DaemonStartOptions,
|
|
851
887
|
): Promise<void> {
|
|
852
888
|
warnIfLegacyWorkspaceFallbackDetected(resources);
|
|
889
|
+
writeAssistantWrapper(resources);
|
|
853
890
|
|
|
854
891
|
const foreground = options?.foreground ?? false;
|
|
855
892
|
// Check for a compiled daemon binary adjacent to the CLI executable.
|
|
@@ -213,6 +213,7 @@ export async function ensureSelfHostedLocalRegistration(
|
|
|
213
213
|
clientPlatform: string,
|
|
214
214
|
assistantVersion?: string,
|
|
215
215
|
platformUrl?: string,
|
|
216
|
+
publicBaseUrl?: string,
|
|
216
217
|
): Promise<EnsureRegistrationResponse> {
|
|
217
218
|
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
218
219
|
const body: Record<string, string> = {
|
|
@@ -223,6 +224,9 @@ export async function ensureSelfHostedLocalRegistration(
|
|
|
223
224
|
if (assistantVersion) {
|
|
224
225
|
body.assistant_version = assistantVersion;
|
|
225
226
|
}
|
|
227
|
+
if (publicBaseUrl) {
|
|
228
|
+
body.public_ingress_url = publicBaseUrl;
|
|
229
|
+
}
|
|
226
230
|
|
|
227
231
|
const response = await fetch(
|
|
228
232
|
`${resolvedUrl}/v1/assistants/self-hosted-local/ensure-registration/`,
|
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Declarative StatefulSet spec for the
|
|
2
|
+
* Declarative StatefulSet spec for the assistant service group.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
8
|
-
* This file is self-contained — it does not import from `docker.ts` to
|
|
9
|
-
* avoid a circular dependency. Constants are inlined from their definitions
|
|
10
|
-
* in `docker.ts` and must be kept in sync if those change.
|
|
4
|
+
* Defines the static topology of all three containers (assistant, gateway,
|
|
5
|
+
* credential-executor), their volumes, ports, and env vars. Used by both
|
|
6
|
+
* the hatch and upgrade flows to build `docker run` argument arrays.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
import { existsSync } from "fs";
|
|
14
10
|
|
|
11
|
+
import {
|
|
12
|
+
ASSISTANT_INTERNAL_PORT,
|
|
13
|
+
GATEWAY_INTERNAL_PORT,
|
|
14
|
+
} from "./environments/paths.js";
|
|
15
15
|
import { PROVIDER_ENV_VAR_NAMES } from "../shared/provider-env-vars.js";
|
|
16
16
|
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Constants (mirrored from docker.ts — keep in sync)
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
const GATEWAY_INTERNAL_PORT = 7830;
|
|
22
|
-
const ASSISTANT_INTERNAL_PORT = 7821;
|
|
23
17
|
const AVATAR_DEVICE_ENV_VAR = "VELLUM_AVATAR_DEVICE";
|
|
24
18
|
|
|
25
19
|
/** Logical service name used throughout the CLI. */
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
2
|
import { spawnSync } from "child_process";
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
4
|
-
import { homedir } from "os";
|
|
5
4
|
import { join } from "path";
|
|
6
5
|
|
|
7
6
|
import type { AssistantEntry } from "./assistant-config.js";
|
|
@@ -16,6 +15,8 @@ import {
|
|
|
16
15
|
startContainers,
|
|
17
16
|
stopContainers,
|
|
18
17
|
} from "./docker.js";
|
|
18
|
+
import { getStateDir } from "./environments/paths.js";
|
|
19
|
+
import { getCurrentEnvironment } from "./environments/resolve.js";
|
|
19
20
|
import { loadGuardianToken } from "./guardian-token.js";
|
|
20
21
|
import { getPlatformUrl } from "./platform-client.js";
|
|
21
22
|
import { resolveImageRefs } from "./platform-releases.js";
|
|
@@ -26,11 +27,9 @@ import { compareVersions } from "./version-compat.js";
|
|
|
26
27
|
// Failure log capture
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
28
29
|
|
|
29
|
-
/** XDG-compliant directory for upgrade failure logs */
|
|
30
|
+
/** XDG-compliant directory for upgrade failure logs, scoped to the current environment. */
|
|
30
31
|
function getUpgradeLogsDir(): string {
|
|
31
|
-
|
|
32
|
-
process.env.XDG_STATE_HOME?.trim() || join(homedir(), ".local", "state");
|
|
33
|
-
return join(stateHome, "vellum", "upgrade-logs");
|
|
32
|
+
return join(getStateDir(getCurrentEnvironment()), "upgrade-logs");
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/**
|
|
@@ -207,6 +206,38 @@ export async function fetchCurrentVersion(
|
|
|
207
206
|
return undefined;
|
|
208
207
|
}
|
|
209
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Best-effort fetch of the assistant's configured public ingress URL from the
|
|
211
|
+
* gateway `integrations/ingress/config` endpoint. Returns `undefined` when
|
|
212
|
+
* the gateway is unreachable, the bearer token is missing, or no public URL
|
|
213
|
+
* is configured.
|
|
214
|
+
*/
|
|
215
|
+
export async function fetchAssistantIngressUrl(
|
|
216
|
+
runtimeUrl: string,
|
|
217
|
+
bearerToken?: string,
|
|
218
|
+
): Promise<string | undefined> {
|
|
219
|
+
if (!bearerToken) return undefined;
|
|
220
|
+
try {
|
|
221
|
+
const resp = await fetch(`${runtimeUrl}/integrations/ingress/config`, {
|
|
222
|
+
headers: { Authorization: `Bearer ${bearerToken}` },
|
|
223
|
+
signal: AbortSignal.timeout(5000),
|
|
224
|
+
});
|
|
225
|
+
if (resp.ok) {
|
|
226
|
+
const body = (await resp.json()) as {
|
|
227
|
+
publicBaseUrl?: string;
|
|
228
|
+
managedCallbacks?: boolean;
|
|
229
|
+
};
|
|
230
|
+
// Ignore managed-callback URLs — those belong to the platform, not the
|
|
231
|
+
// self-hosted assistant's own ingress.
|
|
232
|
+
if (body.managedCallbacks) return undefined;
|
|
233
|
+
return body.publicBaseUrl || undefined;
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
// Best-effort
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
210
241
|
/**
|
|
211
242
|
* Determine the version that was running before the current one.
|
|
212
243
|
*
|
|
@@ -605,7 +636,7 @@ export async function performDockerRollback(
|
|
|
605
636
|
const signingKey =
|
|
606
637
|
capturedEnv["ACTOR_TOKEN_SIGNING_KEY"] || randomBytes(32).toString("hex");
|
|
607
638
|
|
|
608
|
-
// Build extra env vars, excluding keys managed by
|
|
639
|
+
// Build extra env vars, excluding keys managed by buildServiceRunArgs
|
|
609
640
|
const envKeysSetByRunArgs = new Set(CONTAINER_ENV_EXCLUDE_KEYS);
|
|
610
641
|
for (const envVar of ["ANTHROPIC_API_KEY", "VELLUM_PLATFORM_URL"]) {
|
|
611
642
|
if (process.env[envVar]) {
|