@vellumai/cli 0.5.6 → 0.5.8

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/src/lib/docker.ts CHANGED
@@ -12,11 +12,13 @@ import {
12
12
  setActiveAssistant,
13
13
  } from "./assistant-config";
14
14
  import type { AssistantEntry } from "./assistant-config";
15
+ import { writeInitialConfig } from "./config-utils";
15
16
  import { DEFAULT_GATEWAY_PORT, PROVIDER_ENV_VAR_NAMES } from "./constants";
16
17
  import type { Species } from "./constants";
17
- import { leaseGuardianToken, saveBootstrapSecret } from "./guardian-token";
18
+ import { leaseGuardianToken } from "./guardian-token";
18
19
  import { isVellumProcess, stopProcess } from "./process";
19
20
  import { generateInstanceName } from "./random-name";
21
+ import { resolveImageRefs } from "./platform-releases.js";
20
22
  import { exec, execOutput } from "./step-runner";
21
23
  import {
22
24
  closeLogFile,
@@ -269,11 +271,30 @@ async function ensureDockerInstalled(): Promise<void> {
269
271
  console.log("🚀 Docker daemon not running. Starting Colima...");
270
272
  try {
271
273
  await exec("colima", ["start"]);
272
- } catch (err) {
273
- const message = err instanceof Error ? err.message : String(err);
274
- throw new Error(
275
- `Failed to start Colima. Please run 'colima start' manually.\n${message}`,
274
+ } catch {
275
+ // Colima may fail if a previous VM instance is in a corrupt state.
276
+ // Attempt to delete the stale instance and retry once.
277
+ console.log(
278
+ "⚠️ Colima start failed — attempting to reset stale VM state...",
276
279
  );
280
+ try {
281
+ await exec("colima", ["stop", "--force"]).catch(() => {});
282
+ await exec("colima", ["delete", "--force"]);
283
+ } catch {
284
+ // If delete also fails, fall through to the retry which will
285
+ // produce a clear error message.
286
+ }
287
+
288
+ try {
289
+ console.log("🔄 Retrying colima start...");
290
+ await exec("colima", ["start"]);
291
+ } catch (retryErr) {
292
+ const message =
293
+ retryErr instanceof Error ? retryErr.message : String(retryErr);
294
+ throw new Error(
295
+ `Failed to start Colima after resetting stale VM state. Please run 'colima start' manually.\n${message}`,
296
+ );
297
+ }
277
298
  }
278
299
  }
279
300
  }
@@ -464,16 +485,19 @@ async function buildAllImages(
464
485
  * can be restarted independently.
465
486
  */
466
487
  export function serviceDockerRunArgs(opts: {
488
+ signingKey?: string;
467
489
  bootstrapSecret?: string;
468
490
  cesServiceToken?: string;
469
491
  extraAssistantEnv?: Record<string, string>;
470
492
  gatewayPort: number;
471
493
  imageTags: Record<ServiceName, string>;
494
+ defaultWorkspaceConfigPath?: string;
472
495
  instanceName: string;
473
496
  res: ReturnType<typeof dockerResourceNames>;
474
497
  }): Record<ServiceName, () => string[]> {
475
498
  const {
476
499
  cesServiceToken,
500
+ defaultWorkspaceConfigPath,
477
501
  extraAssistantEnv,
478
502
  gatewayPort,
479
503
  imageTags,
@@ -496,17 +520,31 @@ export function serviceDockerRunArgs(opts: {
496
520
  "-e",
497
521
  `VELLUM_ASSISTANT_NAME=${instanceName}`,
498
522
  "-e",
523
+ "VELLUM_CLOUD=docker",
524
+ "-e",
499
525
  "RUNTIME_HTTP_HOST=0.0.0.0",
500
526
  "-e",
501
- "WORKSPACE_DIR=/workspace",
527
+ "VELLUM_WORKSPACE_DIR=/workspace",
502
528
  "-e",
503
529
  `CES_CREDENTIAL_URL=http://${res.cesContainer}:8090`,
504
530
  "-e",
505
531
  `GATEWAY_INTERNAL_URL=http://${res.gatewayContainer}:${GATEWAY_INTERNAL_PORT}`,
506
532
  ];
533
+ if (defaultWorkspaceConfigPath) {
534
+ const containerPath = `/tmp/vellum-default-workspace-config-${Date.now()}.json`;
535
+ args.push(
536
+ "-v",
537
+ `${defaultWorkspaceConfigPath}:${containerPath}:ro`,
538
+ "-e",
539
+ `VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH=${containerPath}`,
540
+ );
541
+ }
507
542
  if (cesServiceToken) {
508
543
  args.push("-e", `CES_SERVICE_TOKEN=${cesServiceToken}`);
509
544
  }
545
+ if (opts.signingKey) {
546
+ args.push("-e", `ACTOR_TOKEN_SIGNING_KEY=${opts.signingKey}`);
547
+ }
510
548
  for (const envVar of [
511
549
  ...Object.values(PROVIDER_ENV_VAR_NAMES),
512
550
  "VELLUM_PLATFORM_URL",
@@ -537,7 +575,7 @@ export function serviceDockerRunArgs(opts: {
537
575
  "-v",
538
576
  `${res.gatewaySecurityVolume}:/gateway-security`,
539
577
  "-e",
540
- "WORKSPACE_DIR=/workspace",
578
+ "VELLUM_WORKSPACE_DIR=/workspace",
541
579
  "-e",
542
580
  "GATEWAY_SECURITY_DIR=/gateway-security",
543
581
  "-e",
@@ -553,6 +591,9 @@ export function serviceDockerRunArgs(opts: {
553
591
  ...(cesServiceToken
554
592
  ? ["-e", `CES_SERVICE_TOKEN=${cesServiceToken}`]
555
593
  : []),
594
+ ...(opts.signingKey
595
+ ? ["-e", `ACTOR_TOKEN_SIGNING_KEY=${opts.signingKey}`]
596
+ : []),
556
597
  ...(opts.bootstrapSecret
557
598
  ? ["-e", `GUARDIAN_BOOTSTRAP_SECRET=${opts.bootstrapSecret}`]
558
599
  : []),
@@ -574,7 +615,7 @@ export function serviceDockerRunArgs(opts: {
574
615
  "-e",
575
616
  "CES_MODE=managed",
576
617
  "-e",
577
- "WORKSPACE_DIR=/workspace",
618
+ "VELLUM_WORKSPACE_DIR=/workspace",
578
619
  "-e",
579
620
  "CES_BOOTSTRAP_SOCKET_DIR=/run/ces-bootstrap",
580
621
  "-e",
@@ -739,11 +780,13 @@ export const SERVICE_START_ORDER: ServiceName[] = [
739
780
  /** Start all three containers in dependency order. */
740
781
  export async function startContainers(
741
782
  opts: {
783
+ signingKey?: string;
742
784
  bootstrapSecret?: string;
743
785
  cesServiceToken?: string;
744
786
  extraAssistantEnv?: Record<string, string>;
745
787
  gatewayPort: number;
746
788
  imageTags: Record<ServiceName, string>;
789
+ defaultWorkspaceConfigPath?: string;
747
790
  instanceName: string;
748
791
  res: ReturnType<typeof dockerResourceNames>;
749
792
  },
@@ -765,27 +808,6 @@ export async function stopContainers(
765
808
  await removeContainer(res.assistantContainer);
766
809
  }
767
810
 
768
- /**
769
- * Remove the signing-key-bootstrap lockfile from the gateway security volume.
770
- * This allows the daemon to re-fetch the signing key from the gateway on the
771
- * next startup — necessary during upgrades where the gateway may generate a
772
- * new key.
773
- */
774
- export async function clearSigningKeyBootstrapLock(
775
- res: ReturnType<typeof dockerResourceNames>,
776
- ): Promise<void> {
777
- await exec("docker", [
778
- "run",
779
- "--rm",
780
- "-v",
781
- `${res.gatewaySecurityVolume}:/gateway-security`,
782
- "busybox",
783
- "rm",
784
- "-f",
785
- "/gateway-security/signing-key-bootstrap.lock",
786
- ]);
787
- }
788
-
789
811
  /** Stop containers without removing them (preserves state for `docker start`). */
790
812
  export async function sleepContainers(
791
813
  res: ReturnType<typeof dockerResourceNames>,
@@ -1028,6 +1050,7 @@ export async function hatchDocker(
1028
1050
  detached: boolean,
1029
1051
  name: string | null,
1030
1052
  watch: boolean = false,
1053
+ configValues: Record<string, string> = {},
1031
1054
  ): Promise<void> {
1032
1055
  resetLogFile("hatch.log");
1033
1056
 
@@ -1074,14 +1097,16 @@ export async function hatchDocker(
1074
1097
  } else {
1075
1098
  const version = cliPkg.version;
1076
1099
  const versionTag = version ? `v${version}` : "latest";
1077
- imageTags.assistant = `${DOCKERHUB_IMAGES.assistant}:${versionTag}`;
1078
- imageTags.gateway = `${DOCKERHUB_IMAGES.gateway}:${versionTag}`;
1100
+ log("🔍 Resolving image references...");
1101
+ const resolved = await resolveImageRefs(versionTag, log);
1102
+ imageTags.assistant = resolved.imageTags.assistant;
1103
+ imageTags.gateway = resolved.imageTags.gateway;
1079
1104
  imageTags["credential-executor"] =
1080
- `${DOCKERHUB_IMAGES["credential-executor"]}:${versionTag}`;
1105
+ resolved.imageTags["credential-executor"];
1081
1106
 
1082
1107
  log(`🥚 Hatching Docker assistant: ${instanceName}`);
1083
1108
  log(` Species: ${species}`);
1084
- log(` Images:`);
1109
+ log(` Images (${resolved.source}):`);
1085
1110
  log(` assistant: ${imageTags.assistant}`);
1086
1111
  log(` gateway: ${imageTags.gateway}`);
1087
1112
  log(` credential-executor: ${imageTags["credential-executor"]}`);
@@ -1115,24 +1140,35 @@ export async function hatchDocker(
1115
1140
  "/workspace",
1116
1141
  ]);
1117
1142
 
1118
- // Clear any stale signing-key bootstrap lockfile so the daemon can
1119
- // fetch the key from the gateway on first startup.
1120
- await exec("docker", [
1121
- "run",
1122
- "--rm",
1123
- "-v",
1124
- `${res.gatewaySecurityVolume}:/gateway-security`,
1125
- "busybox",
1126
- "rm",
1127
- "-f",
1128
- "/gateway-security/signing-key-bootstrap.lock",
1129
- ]);
1143
+ // Write --config key=value pairs to a temp file that gets bind-mounted
1144
+ // into the assistant container and read via VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH.
1145
+ const defaultWorkspaceConfigPath = writeInitialConfig(configValues);
1130
1146
 
1131
1147
  const cesServiceToken = randomBytes(32).toString("hex");
1132
- const bootstrapSecret = randomBytes(32).toString("hex");
1133
- saveBootstrapSecret(instanceName, bootstrapSecret);
1148
+ const signingKey = randomBytes(32).toString("hex");
1149
+
1150
+ // When launched by a remote hatch startup script, the env var
1151
+ // GUARDIAN_BOOTSTRAP_SECRET is already set with the laptop's secret.
1152
+ // Generate a new secret for the local docker hatch caller and append
1153
+ // it so the gateway receives a comma-separated list of all expected
1154
+ // bootstrap secrets.
1155
+ const ownSecret = randomBytes(32).toString("hex");
1156
+ const preExisting = process.env.GUARDIAN_BOOTSTRAP_SECRET;
1157
+ const bootstrapSecret = preExisting
1158
+ ? `${preExisting},${ownSecret}`
1159
+ : ownSecret;
1160
+
1134
1161
  await startContainers(
1135
- { bootstrapSecret, cesServiceToken, gatewayPort, imageTags, instanceName, res },
1162
+ {
1163
+ signingKey,
1164
+ bootstrapSecret,
1165
+ cesServiceToken,
1166
+ gatewayPort,
1167
+ imageTags,
1168
+ defaultWorkspaceConfigPath,
1169
+ instanceName,
1170
+ res,
1171
+ },
1136
1172
  log,
1137
1173
  );
1138
1174
 
@@ -1161,6 +1197,7 @@ export async function hatchDocker(
1161
1197
  setActiveAssistant(instanceName);
1162
1198
 
1163
1199
  const { ready } = await waitForGatewayAndLease({
1200
+ bootstrapSecret: ownSecret,
1164
1201
  containerName: res.assistantContainer,
1165
1202
  detached: watch ? false : detached,
1166
1203
  instanceName,
@@ -1218,13 +1255,21 @@ export async function hatchDocker(
1218
1255
  * lease a guardian token.
1219
1256
  */
1220
1257
  async function waitForGatewayAndLease(opts: {
1258
+ bootstrapSecret: string;
1221
1259
  containerName: string;
1222
1260
  detached: boolean;
1223
1261
  instanceName: string;
1224
1262
  logFd: number | "ignore";
1225
1263
  runtimeUrl: string;
1226
1264
  }): Promise<{ ready: boolean }> {
1227
- const { containerName, detached, instanceName, logFd, runtimeUrl } = opts;
1265
+ const {
1266
+ bootstrapSecret,
1267
+ containerName,
1268
+ detached,
1269
+ instanceName,
1270
+ logFd,
1271
+ runtimeUrl,
1272
+ } = opts;
1228
1273
 
1229
1274
  const log = (msg: string): void => {
1230
1275
  console.log(msg);
@@ -1298,7 +1343,11 @@ async function waitForGatewayAndLease(opts: {
1298
1343
 
1299
1344
  while (Date.now() < leaseDeadline) {
1300
1345
  try {
1301
- const tokenData = await leaseGuardianToken(runtimeUrl, instanceName);
1346
+ const tokenData = await leaseGuardianToken(
1347
+ runtimeUrl,
1348
+ instanceName,
1349
+ bootstrapSecret,
1350
+ );
1302
1351
  const leaseElapsed = ((Date.now() - leaseStart) / 1000).toFixed(1);
1303
1352
  log(
1304
1353
  `Guardian token lease: success after ${leaseElapsed}s (principalId=${tokenData.guardianPrincipalId}, expiresAt=${tokenData.accessTokenExpiresAt})`,
@@ -1,4 +1,4 @@
1
- const DOCTOR_URL = "https://doctor.vellum.ai";
1
+ const DOCTOR_URL = process.env.DOCTOR_SERVICE_URL?.trim() || "";
2
2
 
3
3
  export type ProgressPhase =
4
4
  | "invoking_prompt"
@@ -107,6 +107,16 @@ async function callDoctorDaemon(
107
107
  chatContext?: ChatLogEntry[],
108
108
  onLog?: DoctorLogCallback,
109
109
  ): Promise<DoctorResult> {
110
+ if (!DOCTOR_URL) {
111
+ onLog?.("Doctor service not configured (DOCTOR_SERVICE_URL is not set)");
112
+ return {
113
+ assistantId,
114
+ diagnostics: null,
115
+ recommendation: null,
116
+ error: "Doctor service not configured",
117
+ };
118
+ }
119
+
110
120
  const MAX_RETRIES = 2;
111
121
  let lastError: unknown;
112
122
 
package/src/lib/gcp.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  } from "./constants";
12
12
  import type { Species } from "./constants";
13
13
  import { leaseGuardianToken } from "./guardian-token";
14
+ import { getPlatformUrl } from "./platform-client";
14
15
  import { generateInstanceName } from "./random-name";
15
16
  import { exec, execOutput } from "./step-runner";
16
17
 
@@ -455,13 +456,15 @@ export async function hatchGcp(
455
456
  providerApiKeys: Record<string, string>,
456
457
  instanceName: string,
457
458
  cloud: "gcp",
458
- ) => Promise<string>,
459
+ configValues?: Record<string, string>,
460
+ ) => Promise<{ script: string; laptopBootstrapSecret: string }>,
459
461
  watchHatching: (
460
462
  pollFn: () => Promise<PollResult>,
461
463
  instanceName: string,
462
464
  startTime: number,
463
465
  species: Species,
464
466
  ) => Promise<WatchHatchingResult>,
467
+ configValues: Record<string, string> = {},
465
468
  ): Promise<void> {
466
469
  const startTime = Date.now();
467
470
  const account = process.env.GCP_ACCOUNT_EMAIL;
@@ -519,13 +522,15 @@ export async function hatchGcp(
519
522
  );
520
523
  process.exit(1);
521
524
  }
522
- const startupScript = await buildStartupScript(
523
- species,
524
- sshUser,
525
- providerApiKeys,
526
- instanceName,
527
- "gcp",
528
- );
525
+ const { script: startupScript, laptopBootstrapSecret } =
526
+ await buildStartupScript(
527
+ species,
528
+ sshUser,
529
+ providerApiKeys,
530
+ instanceName,
531
+ "gcp",
532
+ configValues,
533
+ );
529
534
  const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`);
530
535
  writeFileSync(startupScriptPath, startupScript);
531
536
 
@@ -640,7 +645,7 @@ export async function hatchGcp(
640
645
  species === "vellum" &&
641
646
  (await checkCurlFailure(instanceName, project, zone, account))
642
647
  ) {
643
- const installScriptUrl = `${process.env.VELLUM_PLATFORM_URL ?? "https://vellum.ai"}/install.sh`;
648
+ const installScriptUrl = `${getPlatformUrl()}/install.sh`;
644
649
  console.log(
645
650
  `\ud83d\udd04 Detected install script curl failure for ${installScriptUrl}, attempting recovery...`,
646
651
  );
@@ -658,7 +663,11 @@ export async function hatchGcp(
658
663
  }
659
664
 
660
665
  try {
661
- await leaseGuardianToken(runtimeUrl, instanceName);
666
+ await leaseGuardianToken(
667
+ runtimeUrl,
668
+ instanceName,
669
+ laptopBootstrapSecret,
670
+ );
662
671
  } catch (err) {
663
672
  console.warn(
664
673
  `\u26a0\ufe0f Could not lease guardian token: ${err instanceof Error ? err.message : err}`,
@@ -42,46 +42,6 @@ function getPersistedDeviceIdPath(): string {
42
42
  return join(getXdgConfigHome(), "vellum", "device-id");
43
43
  }
44
44
 
45
- function getBootstrapSecretPath(assistantId: string): string {
46
- return join(
47
- getXdgConfigHome(),
48
- "vellum",
49
- "assistants",
50
- assistantId,
51
- "bootstrap-secret",
52
- );
53
- }
54
-
55
- /**
56
- * Load a previously saved bootstrap secret for the given assistant.
57
- * Returns null if the file does not exist or is unreadable.
58
- */
59
- export function loadBootstrapSecret(assistantId: string): string | null {
60
- try {
61
- const raw = readFileSync(getBootstrapSecretPath(assistantId), "utf-8").trim();
62
- return raw.length > 0 ? raw : null;
63
- } catch {
64
- return null;
65
- }
66
- }
67
-
68
- /**
69
- * Persist a bootstrap secret for the given assistant so that the desktop
70
- * client and upgrade/rollback paths can retrieve it later.
71
- */
72
- export function saveBootstrapSecret(
73
- assistantId: string,
74
- secret: string,
75
- ): void {
76
- const path = getBootstrapSecretPath(assistantId);
77
- const dir = dirname(path);
78
- if (!existsSync(dir)) {
79
- mkdirSync(dir, { recursive: true, mode: 0o700 });
80
- }
81
- writeFileSync(path, secret + "\n", { mode: 0o600 });
82
- chmodSync(path, 0o600);
83
- }
84
-
85
45
  function hashWithSalt(input: string): string {
86
46
  return createHash("sha256")
87
47
  .update(input + DEVICE_ID_SALT)
@@ -206,10 +166,12 @@ export function saveGuardianToken(
206
166
  export async function leaseGuardianToken(
207
167
  gatewayUrl: string,
208
168
  assistantId: string,
169
+ bootstrapSecret?: string,
209
170
  ): Promise<GuardianTokenData> {
210
171
  const deviceId = computeDeviceId();
211
- const headers: Record<string, string> = { "Content-Type": "application/json" };
212
- const bootstrapSecret = loadBootstrapSecret(assistantId);
172
+ const headers: Record<string, string> = {
173
+ "Content-Type": "application/json",
174
+ };
213
175
  if (bootstrapSecret) {
214
176
  headers["x-bootstrap-secret"] = bootstrapSecret;
215
177
  }
package/src/lib/local.ts CHANGED
@@ -192,10 +192,15 @@ function resolveDaemonMainPath(assistantIndex: string): string {
192
192
  return join(dirname(assistantIndex), "daemon", "main.ts");
193
193
  }
194
194
 
195
+ type DaemonStartOptions = {
196
+ foreground?: boolean;
197
+ defaultWorkspaceConfigPath?: string;
198
+ };
199
+
195
200
  async function startDaemonFromSource(
196
201
  assistantIndex: string,
197
202
  resources: LocalInstanceResources,
198
- options?: { foreground?: boolean },
203
+ options?: DaemonStartOptions,
199
204
  ): Promise<void> {
200
205
  const foreground = options?.foreground ?? false;
201
206
  const daemonMainPath = resolveDaemonMainPath(assistantIndex);
@@ -260,6 +265,7 @@ async function startDaemonFromSource(
260
265
  const env: Record<string, string | undefined> = {
261
266
  ...process.env,
262
267
  RUNTIME_HTTP_PORT: process.env.RUNTIME_HTTP_PORT || "7821",
268
+ VELLUM_CLOUD: "local",
263
269
  };
264
270
  if (resources) {
265
271
  env.BASE_DATA_DIR = resources.instanceDir;
@@ -268,6 +274,10 @@ async function startDaemonFromSource(
268
274
  env.QDRANT_HTTP_PORT = String(resources.qdrantPort);
269
275
  delete env.QDRANT_URL;
270
276
  }
277
+ if (options?.defaultWorkspaceConfigPath) {
278
+ env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH =
279
+ options.defaultWorkspaceConfigPath;
280
+ }
271
281
 
272
282
  // Write a sentinel PID file before spawning so concurrent hatch() calls
273
283
  // detect the in-progress spawn and wait instead of racing.
@@ -306,6 +316,7 @@ async function startDaemonFromSource(
306
316
  async function startDaemonWatchFromSource(
307
317
  assistantIndex: string,
308
318
  resources: LocalInstanceResources,
319
+ options?: DaemonStartOptions,
309
320
  ): Promise<void> {
310
321
  const mainPath = resolveDaemonMainPath(assistantIndex);
311
322
  if (!existsSync(mainPath)) {
@@ -381,6 +392,10 @@ async function startDaemonWatchFromSource(
381
392
  env.QDRANT_HTTP_PORT = String(resources.qdrantPort);
382
393
  delete env.QDRANT_URL;
383
394
  }
395
+ if (options?.defaultWorkspaceConfigPath) {
396
+ env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH =
397
+ options.defaultWorkspaceConfigPath;
398
+ }
384
399
 
385
400
  // Write a sentinel PID file before spawning so concurrent hatch() calls
386
401
  // detect the in-progress spawn and wait instead of racing.
@@ -819,7 +834,7 @@ export function isGatewayWatchModeAvailable(): boolean {
819
834
  export async function startLocalDaemon(
820
835
  watch: boolean = false,
821
836
  resources: LocalInstanceResources,
822
- options?: { foreground?: boolean },
837
+ options?: DaemonStartOptions,
823
838
  ): Promise<void> {
824
839
  const foreground = options?.foreground ?? false;
825
840
  // Check for a compiled daemon binary adjacent to the CLI executable.
@@ -905,7 +920,7 @@ export async function startLocalDaemon(
905
920
 
906
921
  // Build a minimal environment for the daemon. When launched from the
907
922
  // macOS app the CLI inherits a huge environment (XPC_SERVICE_NAME,
908
- // __CFBundleIdentifier, CLAUDE_CODE_ENTRYPOINT, etc.) that can cause
923
+ // __CFBundleIdentifier, etc.) that can cause
909
924
  // the daemon to take 50+ seconds to start instead of ~1s.
910
925
  const home = homedir();
911
926
  const bunBinDir = join(home, ".bun", "bin");
@@ -924,20 +939,26 @@ export async function startLocalDaemon(
924
939
  "ANTHROPIC_API_KEY",
925
940
  "APP_VERSION",
926
941
  "BASE_DATA_DIR",
927
- "PLATFORM_BASE_URL",
942
+ "VELLUM_PLATFORM_URL",
928
943
  "QDRANT_HTTP_PORT",
929
944
  "QDRANT_URL",
930
945
  "RUNTIME_HTTP_PORT",
931
- "SENTRY_DSN",
946
+ "SENTRY_DSN_ASSISTANT",
932
947
  "TMPDIR",
933
948
  "USER",
934
949
  "LANG",
935
950
  "VELLUM_DEBUG",
951
+ "VELLUM_DEV",
952
+ "VELLUM_DESKTOP_APP",
936
953
  ]) {
937
954
  if (process.env[key]) {
938
955
  daemonEnv[key] = process.env[key]!;
939
956
  }
940
957
  }
958
+ if (options?.defaultWorkspaceConfigPath) {
959
+ daemonEnv.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH =
960
+ options.defaultWorkspaceConfigPath;
961
+ }
941
962
  // When running a named instance, override env so the daemon resolves
942
963
  // all paths under the instance directory and listens on its own port.
943
964
  if (resources) {
@@ -1005,9 +1026,9 @@ export async function startLocalDaemon(
1005
1026
  // Kill the bundled daemon to avoid two processes competing for the same port
1006
1027
  await stopProcessByPidFile(pidFile, "bundled daemon");
1007
1028
  if (watch) {
1008
- await startDaemonWatchFromSource(assistantIndex, resources);
1029
+ await startDaemonWatchFromSource(assistantIndex, resources, options);
1009
1030
  } else {
1010
- await startDaemonFromSource(assistantIndex, resources);
1031
+ await startDaemonFromSource(assistantIndex, resources, options);
1011
1032
  }
1012
1033
  daemonReady = await waitForDaemonReady(resources.daemonPort, 60000);
1013
1034
  }
@@ -1031,7 +1052,7 @@ export async function startLocalDaemon(
1031
1052
  );
1032
1053
  }
1033
1054
  if (watch) {
1034
- await startDaemonWatchFromSource(assistantIndex, resources);
1055
+ await startDaemonWatchFromSource(assistantIndex, resources, options);
1035
1056
 
1036
1057
  const daemonReady = await waitForDaemonReady(resources.daemonPort, 60000);
1037
1058
  if (daemonReady) {
@@ -1042,7 +1063,7 @@ export async function startLocalDaemon(
1042
1063
  );
1043
1064
  }
1044
1065
  } else {
1045
- await startDaemonFromSource(assistantIndex, resources, { foreground });
1066
+ await startDaemonFromSource(assistantIndex, resources, options);
1046
1067
 
1047
1068
  const daemonReady = await waitForDaemonReady(resources.daemonPort, 60000);
1048
1069
  if (daemonReady) {