@vellumai/cli 0.12.2 → 0.12.3-staging.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.
Files changed (24) hide show
  1. package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
  2. package/node_modules/@vellumai/environments/src/shell.ts +24 -0
  3. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
  4. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/shell.ts +24 -0
  5. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/package.json +2 -1
  6. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
  7. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  8. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  9. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
  10. package/node_modules/@vellumai/local-mode/src/status.ts +2 -9
  11. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  12. package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  15. package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
  16. package/package.json +1 -1
  17. package/src/commands/roadmap.ts +8 -1
  18. package/src/lib/__tests__/docker.test.ts +1 -0
  19. package/src/lib/__tests__/local-ces.test.ts +25 -2
  20. package/src/lib/local.ts +34 -31
  21. package/src/lib/platform-client.ts +0 -12
  22. package/src/lib/provider-secrets.ts +4 -0
  23. package/src/lib/statefulset.ts +1 -0
  24. package/src/shared/provider-env-vars.ts +3 -0
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  buildShellInvocation,
5
+ buildShellSpawnFlags,
5
6
  pathListDelimiter,
6
7
  prependUniquePathEntries,
7
8
  } from "./shell.js";
@@ -38,6 +39,26 @@ describe("buildShellInvocation", () => {
38
39
  });
39
40
  });
40
41
 
42
+ describe("buildShellSpawnFlags", () => {
43
+ test("creates a POSIX process group and hides Windows consoles", () => {
44
+ expect(buildShellSpawnFlags("linux")).toEqual({
45
+ detached: true,
46
+ windowsHide: true,
47
+ });
48
+ expect(buildShellSpawnFlags("darwin")).toEqual({
49
+ detached: true,
50
+ windowsHide: true,
51
+ });
52
+ });
53
+
54
+ test("does not detach Windows children that use piped stdio", () => {
55
+ expect(buildShellSpawnFlags("win32")).toEqual({
56
+ detached: false,
57
+ windowsHide: true,
58
+ });
59
+ });
60
+ });
61
+
41
62
  describe("path list handling", () => {
42
63
  test("uses the platform delimiter", () => {
43
64
  expect(pathListDelimiter("win32")).toBe(";");
@@ -5,6 +5,11 @@ export interface ShellInvocation {
5
5
  args: string[];
6
6
  }
7
7
 
8
+ export interface ShellSpawnFlags {
9
+ detached: boolean;
10
+ windowsHide: true;
11
+ }
12
+
8
13
  const WINDOWS_UTF8_PREAMBLE =
9
14
  "try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}; " +
10
15
  "$OutputEncoding = [System.Text.Encoding]::UTF8; " +
@@ -43,6 +48,25 @@ export function buildShellInvocation(
43
48
  return { command: "bash", args: ["-c", "--", command] };
44
49
  }
45
50
 
51
+ /**
52
+ * Spawn flags for assistant-owned shell children (sandbox bash, local
53
+ * host_bash fallback, sanitized CLI bash, skill runners, scheduled scripts).
54
+ *
55
+ * POSIX uses a new process group so timeout/abort can SIGKILL the tree via
56
+ * `-pid`. Windows process trees are torn down with `taskkill /T`, which does
57
+ * not need a detached process. Combining `DETACHED_PROCESS`,
58
+ * `CREATE_NO_WINDOW`, and piped stdio on Windows can emit `close` with exit
59
+ * 0 and empty pipes without running the encoded command.
60
+ */
61
+ export function buildShellSpawnFlags(
62
+ hostPlatform: NodeJS.Platform = process.platform,
63
+ ): ShellSpawnFlags {
64
+ return {
65
+ detached: hostPlatform !== "win32",
66
+ windowsHide: true,
67
+ };
68
+ }
69
+
46
70
  export function pathListDelimiter(
47
71
  hostPlatform: NodeJS.Platform = process.platform,
48
72
  ): string {
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  buildShellInvocation,
5
+ buildShellSpawnFlags,
5
6
  pathListDelimiter,
6
7
  prependUniquePathEntries,
7
8
  } from "./shell.js";
@@ -38,6 +39,26 @@ describe("buildShellInvocation", () => {
38
39
  });
39
40
  });
40
41
 
42
+ describe("buildShellSpawnFlags", () => {
43
+ test("creates a POSIX process group and hides Windows consoles", () => {
44
+ expect(buildShellSpawnFlags("linux")).toEqual({
45
+ detached: true,
46
+ windowsHide: true,
47
+ });
48
+ expect(buildShellSpawnFlags("darwin")).toEqual({
49
+ detached: true,
50
+ windowsHide: true,
51
+ });
52
+ });
53
+
54
+ test("does not detach Windows children that use piped stdio", () => {
55
+ expect(buildShellSpawnFlags("win32")).toEqual({
56
+ detached: false,
57
+ windowsHide: true,
58
+ });
59
+ });
60
+ });
61
+
41
62
  describe("path list handling", () => {
42
63
  test("uses the platform delimiter", () => {
43
64
  expect(pathListDelimiter("win32")).toBe(";");
@@ -5,6 +5,11 @@ export interface ShellInvocation {
5
5
  args: string[];
6
6
  }
7
7
 
8
+ export interface ShellSpawnFlags {
9
+ detached: boolean;
10
+ windowsHide: true;
11
+ }
12
+
8
13
  const WINDOWS_UTF8_PREAMBLE =
9
14
  "try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}; " +
10
15
  "$OutputEncoding = [System.Text.Encoding]::UTF8; " +
@@ -43,6 +48,25 @@ export function buildShellInvocation(
43
48
  return { command: "bash", args: ["-c", "--", command] };
44
49
  }
45
50
 
51
+ /**
52
+ * Spawn flags for assistant-owned shell children (sandbox bash, local
53
+ * host_bash fallback, sanitized CLI bash, skill runners, scheduled scripts).
54
+ *
55
+ * POSIX uses a new process group so timeout/abort can SIGKILL the tree via
56
+ * `-pid`. Windows process trees are torn down with `taskkill /T`, which does
57
+ * not need a detached process. Combining `DETACHED_PROCESS`,
58
+ * `CREATE_NO_WINDOW`, and piped stdio on Windows can emit `close` with exit
59
+ * 0 and empty pipes without running the encoded command.
60
+ */
61
+ export function buildShellSpawnFlags(
62
+ hostPlatform: NodeJS.Platform = process.platform,
63
+ ): ShellSpawnFlags {
64
+ return {
65
+ detached: hostPlatform !== "win32",
66
+ windowsHide: true,
67
+ };
68
+ }
69
+
46
70
  export function pathListDelimiter(
47
71
  hostPlatform: NodeJS.Platform = process.platform,
48
72
  ): string {
@@ -27,7 +27,8 @@
27
27
  "./reactions": "./src/reactions.ts",
28
28
  "./guardian-requests": "./src/guardian-requests.ts",
29
29
  "./platform-credential": "./src/platform-credential.ts",
30
- "./plan-credit": "./src/plan-credit.ts"
30
+ "./plan-credit": "./src/plan-credit.ts",
31
+ "./integration-categories": "./src/integration-categories.ts"
31
32
  },
32
33
  "scripts": {
33
34
  "typecheck": "bunx tsc --noEmit",
@@ -63,6 +63,7 @@ const FIXTURES: Record<string, string> = {
63
63
  "Perplexity API Key": `pplx-${filler(40)}`,
64
64
  "Tavily API Key": `tvly-${filler(20)}`,
65
65
  "Firecrawl API Key": `fc-${filler(20)}`,
66
+ "Resend API Key": `re_${filler(8)}_${filler(24)}`,
66
67
  };
67
68
 
68
69
  describe("subpath export", () => {
@@ -29,6 +29,7 @@ export * from "./trust-rules.js";
29
29
  export * from "./ingress.js";
30
30
  export * from "./no-response.js";
31
31
  export * from "./plan-credit.js";
32
+ export * from "./integration-categories.js";
32
33
  export * from "./platform-credential.js";
33
34
  export * from "./remote-web-pairing.js";
34
35
  export * from "./twilio-ingress.js";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Category vocabulary for the integrations catalog, shared by the assistant
3
+ * daemon (which stamps it on OAuth providers and marketplace MCP entries) and
4
+ * the clients (which draw the category filter from it).
5
+ *
6
+ * The order here is the order clients show the categories in. The slug is the
7
+ * wire value; the user-facing label is the client's, so it can be translated.
8
+ */
9
+ export const INTEGRATION_CATEGORIES = [
10
+ "productivity",
11
+ "communication",
12
+ "meetings",
13
+ "sales",
14
+ "marketing",
15
+ "finance",
16
+ "commerce",
17
+ "engineering",
18
+ "knowledge",
19
+ "recruiting",
20
+ ] as const;
21
+
22
+ export type IntegrationCategory = (typeof INTEGRATION_CATEGORIES)[number];
23
+
24
+ const INTEGRATION_CATEGORY_SET: ReadonlySet<string> = new Set(
25
+ INTEGRATION_CATEGORIES,
26
+ );
27
+
28
+ /**
29
+ * Whether `value` names a category this build knows. A client reading a newer
30
+ * catalog treats an unknown slug as uncategorized rather than failing.
31
+ */
32
+ export function isIntegrationCategory(
33
+ value: unknown,
34
+ ): value is IntegrationCategory {
35
+ return typeof value === "string" && INTEGRATION_CATEGORY_SET.has(value);
36
+ }
@@ -230,6 +230,9 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
230
230
 
231
231
  // -- Firecrawl --
232
232
  { label: "Firecrawl API Key", regex: /fc-[A-Za-z0-9]{20,}/ },
233
+ // Resend documents only the `re_` prefix; the segment shape is the observed
234
+ // key format. Strict on purpose: bare `re_` prefixes ordinary identifiers.
235
+ { label: "Resend API Key", regex: /re_[A-Za-z0-9]{8}_[A-Za-z0-9]{24,}/ },
233
236
  ];
234
237
 
235
238
  /**
@@ -362,7 +365,7 @@ export function isPlaceholderValue(value: string): boolean {
362
365
  // Strip known prefixes to isolate the variable part
363
366
  const variablePart = value
364
367
  .replace(
365
- /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
368
+ /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|re_|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
366
369
  "",
367
370
  )
368
371
  .replace(/[^A-Za-z0-9]/g, "");
@@ -222,13 +222,6 @@ function defaultPorts(env: Record<string, string | undefined>): {
222
222
  };
223
223
  }
224
224
 
225
- function defaultInstanceDir(
226
- env: Record<string, string | undefined>,
227
- assistantId: string,
228
- ): string {
229
- return resolveInstanceDir(env, assistantId);
230
- }
231
-
232
225
  function firstString(...values: unknown[]): string | undefined {
233
226
  for (const value of values) {
234
227
  if (typeof value === "string" && value.length > 0) return value;
@@ -302,7 +295,7 @@ export function resolveLockfileInstanceDir(
302
295
  ok: true,
303
296
  instanceDir:
304
297
  lockfileInstanceDir(entry, rawEntry) ??
305
- defaultInstanceDir(env, assistantId),
298
+ resolveInstanceDir(env, assistantId),
306
299
  };
307
300
  } catch {
308
301
  return { ok: true };
@@ -320,7 +313,7 @@ function resolveStatusResources(
320
313
  const ports = defaultPorts(env);
321
314
  const instanceDir =
322
315
  lockfileInstanceDir(entry, rawEntry) ??
323
- defaultInstanceDir(env, entry.assistantId);
316
+ resolveInstanceDir(env, entry.assistantId);
324
317
  return {
325
318
  instanceDir,
326
319
  daemonPort:
@@ -27,7 +27,8 @@
27
27
  "./reactions": "./src/reactions.ts",
28
28
  "./guardian-requests": "./src/guardian-requests.ts",
29
29
  "./platform-credential": "./src/platform-credential.ts",
30
- "./plan-credit": "./src/plan-credit.ts"
30
+ "./plan-credit": "./src/plan-credit.ts",
31
+ "./integration-categories": "./src/integration-categories.ts"
31
32
  },
32
33
  "scripts": {
33
34
  "typecheck": "bunx tsc --noEmit",
@@ -63,6 +63,7 @@ const FIXTURES: Record<string, string> = {
63
63
  "Perplexity API Key": `pplx-${filler(40)}`,
64
64
  "Tavily API Key": `tvly-${filler(20)}`,
65
65
  "Firecrawl API Key": `fc-${filler(20)}`,
66
+ "Resend API Key": `re_${filler(8)}_${filler(24)}`,
66
67
  };
67
68
 
68
69
  describe("subpath export", () => {
@@ -29,6 +29,7 @@ export * from "./trust-rules.js";
29
29
  export * from "./ingress.js";
30
30
  export * from "./no-response.js";
31
31
  export * from "./plan-credit.js";
32
+ export * from "./integration-categories.js";
32
33
  export * from "./platform-credential.js";
33
34
  export * from "./remote-web-pairing.js";
34
35
  export * from "./twilio-ingress.js";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Category vocabulary for the integrations catalog, shared by the assistant
3
+ * daemon (which stamps it on OAuth providers and marketplace MCP entries) and
4
+ * the clients (which draw the category filter from it).
5
+ *
6
+ * The order here is the order clients show the categories in. The slug is the
7
+ * wire value; the user-facing label is the client's, so it can be translated.
8
+ */
9
+ export const INTEGRATION_CATEGORIES = [
10
+ "productivity",
11
+ "communication",
12
+ "meetings",
13
+ "sales",
14
+ "marketing",
15
+ "finance",
16
+ "commerce",
17
+ "engineering",
18
+ "knowledge",
19
+ "recruiting",
20
+ ] as const;
21
+
22
+ export type IntegrationCategory = (typeof INTEGRATION_CATEGORIES)[number];
23
+
24
+ const INTEGRATION_CATEGORY_SET: ReadonlySet<string> = new Set(
25
+ INTEGRATION_CATEGORIES,
26
+ );
27
+
28
+ /**
29
+ * Whether `value` names a category this build knows. A client reading a newer
30
+ * catalog treats an unknown slug as uncategorized rather than failing.
31
+ */
32
+ export function isIntegrationCategory(
33
+ value: unknown,
34
+ ): value is IntegrationCategory {
35
+ return typeof value === "string" && INTEGRATION_CATEGORY_SET.has(value);
36
+ }
@@ -230,6 +230,9 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
230
230
 
231
231
  // -- Firecrawl --
232
232
  { label: "Firecrawl API Key", regex: /fc-[A-Za-z0-9]{20,}/ },
233
+ // Resend documents only the `re_` prefix; the segment shape is the observed
234
+ // key format. Strict on purpose: bare `re_` prefixes ordinary identifiers.
235
+ { label: "Resend API Key", regex: /re_[A-Za-z0-9]{8}_[A-Za-z0-9]{24,}/ },
233
236
  ];
234
237
 
235
238
  /**
@@ -362,7 +365,7 @@ export function isPlaceholderValue(value: string): boolean {
362
365
  // Strip known prefixes to isolate the variable part
363
366
  const variablePart = value
364
367
  .replace(
365
- /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
368
+ /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|re_|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
366
369
  "",
367
370
  )
368
371
  .replace(/[^A-Za-z0-9]/g, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.12.2",
3
+ "version": "0.12.3-staging.2",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,13 +21,20 @@ function printUsage(): void {
21
21
  console.log(" upvote <slug>");
22
22
  console.log(" unvote <slug>");
23
23
  console.log("");
24
+ console.log(
25
+ "update: --status and --tag are staff-only. Anyone else can edit only the",
26
+ );
27
+ console.log(
28
+ "title and description of an item they filed, while it is still open.",
29
+ );
30
+ console.log("");
24
31
  console.log("Examples:");
25
32
  console.log(' $ vellum roadmap list --query "dark mode"');
26
33
  console.log(" $ vellum roadmap list --status planned --sort upvotes");
27
34
  console.log(" $ vellum roadmap get my-feature-slug");
28
35
  console.log(' $ vellum roadmap create --title "Add dark mode"');
29
36
  console.log(
30
- " $ vellum roadmap update my-feature --status planned --tag integrations",
37
+ ' $ vellum roadmap update my-feature --description "Follow the OS setting"',
31
38
  );
32
39
  console.log(" $ vellum roadmap upvote my-feature-slug");
33
40
  }
@@ -83,6 +83,7 @@ describe("buildServiceRunArgs — assistant", () => {
83
83
  const args = buildAssistantArgs();
84
84
  expect(args).toContain("IS_CONTAINERIZED=true");
85
85
  expect(args).toContain("VELLUM_WORKSPACE_DIR=/workspace");
86
+ expect(args).toContain("CES_BOOTSTRAP_SOCKET_DIR=/run/ces-bootstrap");
86
87
  expect(args).toContain(`VELLUM_ASSISTANT_NAME=${instanceName}`);
87
88
  });
88
89
 
@@ -9,6 +9,8 @@ import { tmpdir } from "node:os";
9
9
  import { join } from "node:path";
10
10
  import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
11
11
 
12
+ import { resolveIpcEndpoint } from "@vellumai/ipc-server-utils";
13
+
12
14
  import { resolveCesSocketPath, startCes } from "../local.js";
13
15
 
14
16
  // ---------------------------------------------------------------------------
@@ -106,7 +108,7 @@ describe("startCes", () => {
106
108
  // startCes unlinks the stale socket, then polls for it. We create it with
107
109
  // a slight delay so the poll catches it.
108
110
  setTimeout(() => {
109
- const socketDir = join(vellumDir, "workspace");
111
+ const socketDir = join(vellumDir, "protected", "credential-executor");
110
112
  mkdirSync(socketDir, { recursive: true });
111
113
  writeFileSync(join(socketDir, "ces.sock"), "");
112
114
  }, 50);
@@ -126,7 +128,9 @@ describe("startCes", () => {
126
128
 
127
129
  // Verify env vars
128
130
  const env = lastSpawnCall!.options.env!;
129
- expect(env["CES_LOCAL_SOCKET"]).toBeDefined();
131
+ expect(env["CES_BOOTSTRAP_SOCKET_DIR"]).toBe(
132
+ join(tempDir, ".vellum", "protected", "credential-executor"),
133
+ );
130
134
  expect(env["CREDENTIAL_SECURITY_DIR"]).toBeDefined();
131
135
  expect(env["VELLUM_WORKSPACE_DIR"]).toBeDefined();
132
136
 
@@ -143,4 +147,23 @@ describe("startCes", () => {
143
147
  expect(socketPath.startsWith("\\\\.\\pipe\\vellum-ces-")).toBe(true);
144
148
  expect(socketPath).not.toContain("Example");
145
149
  });
150
+
151
+ test("POSIX local CES seam matches resolveIpcEndpoint", () => {
152
+ const resources = {
153
+ instanceDir: "/tmp/assistant-123",
154
+ } as Parameters<typeof resolveCesSocketPath>[0];
155
+
156
+ const socketPath = resolveCesSocketPath(resources, "linux");
157
+ expect(socketPath).toBe(
158
+ resolveIpcEndpoint("ces", {
159
+ workspaceDir: join(
160
+ "/tmp/assistant-123",
161
+ ".vellum",
162
+ "protected",
163
+ "credential-executor",
164
+ ),
165
+ platform: "linux",
166
+ }).path,
167
+ );
168
+ });
146
169
  });
package/src/lib/local.ts CHANGED
@@ -606,6 +606,24 @@ type DaemonStartOptions = {
606
606
  signingKey?: string;
607
607
  };
608
608
 
609
+ /**
610
+ * Directory that holds the local CES Unix socket (`ces.sock`).
611
+ *
612
+ * Matches CES's local data root: `{CREDENTIAL_SECURITY_DIR}/credential-executor`.
613
+ * Local CLI and managed sidecars share `CES_BOOTSTRAP_SOCKET_DIR`; this is the
614
+ * writable per-instance directory used outside containers.
615
+ */
616
+ function resolveCesBootstrapSocketDir(
617
+ resources?: LocalInstanceResources,
618
+ env?: Record<string, string | undefined>,
619
+ ): string {
620
+ const securityDir = resources
621
+ ? join(resources.instanceDir, ".vellum", "protected")
622
+ : env?.CREDENTIAL_SECURITY_DIR?.trim() ||
623
+ join(homedir(), ".vellum", "protected");
624
+ return join(securityDir, "credential-executor");
625
+ }
626
+
609
627
  /**
610
628
  * Apply per-instance resource overrides and shared daemon options to an
611
629
  * environment object. Called from all daemon spawn paths (source, watch,
@@ -644,11 +662,10 @@ function applyDaemonEnvOverrides(
644
662
  env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH =
645
663
  options.defaultWorkspaceConfigPath;
646
664
  }
647
- // Pin the daemon to the exact socket the sibling binds so the two agree
648
- // regardless of any stale CES_LOCAL_SOCKET inherited from the parent
649
- // environment. The assistant connects to the sibling instead of spawning
650
- // its own CES.
651
- env.CES_LOCAL_SOCKET = resolveCesSocketPath(resources);
665
+ // CES and the assistant resolve the socket from CES_BOOTSTRAP_SOCKET_DIR.
666
+ const bootstrapDir = resolveCesBootstrapSocketDir(resources, env);
667
+ env.CES_BOOTSTRAP_SOCKET_DIR = bootstrapDir;
668
+ mkdirSync(bootstrapDir, { recursive: true });
652
669
  applyIpcSocketDirOverride(env);
653
670
  }
654
671
 
@@ -1022,26 +1039,18 @@ function resolveCesDir(resources?: LocalInstanceResources): string {
1022
1039
 
1023
1040
  /**
1024
1041
  * Resolve the local IPC endpoint shared by the CLI-launched CES sibling and
1025
- * assistant. Windows uses a named pipe. POSIX uses a Unix socket, including
1026
- * the existing short macOS fallback.
1042
+ * assistant. CES binds this path from `CES_BOOTSTRAP_SOCKET_DIR` via the same
1043
+ * `resolveIpcEndpoint("ces")` helper. Windows uses a named pipe. POSIX uses
1044
+ * a Unix socket, including the shared macOS AF_UNIX fallback.
1027
1045
  */
1028
1046
  export function resolveCesSocketPath(
1029
1047
  resources?: LocalInstanceResources,
1030
1048
  hostPlatform: NodeJS.Platform = platform(),
1031
1049
  ): string {
1032
- const workspaceDir = resources
1033
- ? join(resources.instanceDir, ".vellum", "workspace")
1034
- : join(homedir(), ".vellum", "workspace");
1035
- if (hostPlatform === "win32") {
1036
- return resolveIpcEndpoint("ces", {
1037
- workspaceDir,
1038
- platform: hostPlatform,
1039
- }).path;
1040
- }
1041
- const override = computeIpcSocketDirOverride(workspaceDir);
1042
- const socketDir = override ?? workspaceDir;
1043
- mkdirSync(socketDir, { recursive: true });
1044
- return join(socketDir, "ces.sock");
1050
+ return resolveIpcEndpoint("ces", {
1051
+ workspaceDir: resolveCesBootstrapSocketDir(resources),
1052
+ platform: hostPlatform,
1053
+ }).path;
1045
1054
  }
1046
1055
 
1047
1056
  async function isIpcEndpointReady(endpointPath: string): Promise<boolean> {
@@ -1101,13 +1110,15 @@ export async function startCes(
1101
1110
  const workspaceDir = resources
1102
1111
  ? join(resources.instanceDir, ".vellum", "workspace")
1103
1112
  : join(homedir(), ".vellum", "workspace");
1113
+ const bootstrapDir = resolveCesBootstrapSocketDir(resources);
1104
1114
  mkdirSync(securityDir, { recursive: true });
1115
+ mkdirSync(bootstrapDir, { recursive: true });
1105
1116
 
1106
1117
  const cesEnv: Record<string, string | undefined> = {
1107
1118
  ...process.env,
1108
- CES_LOCAL_SOCKET: socketPath,
1109
1119
  CREDENTIAL_SECURITY_DIR: securityDir,
1110
1120
  VELLUM_WORKSPACE_DIR: workspaceDir,
1121
+ CES_BOOTSTRAP_SOCKET_DIR: bootstrapDir,
1111
1122
  };
1112
1123
 
1113
1124
  let ces;
@@ -1170,14 +1181,6 @@ export async function startCes(
1170
1181
  }
1171
1182
  }
1172
1183
 
1173
- /**
1174
- * Check if the daemon is responsive by hitting its HTTP `/healthz` endpoint.
1175
- * This replaces the socket-based `isSocketResponsive()` check.
1176
- */
1177
- async function isDaemonResponsive(daemonPort: number): Promise<boolean> {
1178
- return httpHealthCheck(daemonPort);
1179
- }
1180
-
1181
1184
  /**
1182
1185
  * Find the PID of the process listening on the given TCP port.
1183
1186
  * Uses `lsof` on macOS/Linux. Returns undefined if no listener is found
@@ -1273,7 +1276,7 @@ async function checkOrphanedDaemon(
1273
1276
  pidFile: string,
1274
1277
  daemonPort: number,
1275
1278
  ): Promise<boolean> {
1276
- if (!(await isDaemonResponsive(daemonPort))) return false;
1279
+ if (!(await httpHealthCheck(daemonPort))) return false;
1277
1280
 
1278
1281
  const recoveredPid = recoverPidFile(pidFile, daemonPort);
1279
1282
  if (recoveredPid) {
@@ -1671,7 +1674,7 @@ export async function startLocalDaemon(
1671
1674
  const daemonSpawnEnv = envWithCompiledRuntimeNodePath(daemonEnv);
1672
1675
 
1673
1676
  // Write a sentinel PID file before spawning so concurrent hatch() calls
1674
- // see the file and fall through to the isDaemonResponsive() port check
1677
+ // see the file and fall through to the httpHealthCheck() port check
1675
1678
  // instead of racing to spawn a duplicate daemon.
1676
1679
  writeFileSync(pidFile, "starting", "utf-8");
1677
1680
 
@@ -528,20 +528,8 @@ export async function injectCredentialsIntoAssistant(
528
528
  promises.push(inject("vellum:assistant_api_key", params.assistantApiKey));
529
529
  }
530
530
 
531
- promises.push(
532
- inject("vellum:platform_assistant_id", params.platformAssistantId),
533
- );
534
-
535
531
  promises.push(inject("vellum:platform_base_url", params.platformBaseUrl));
536
532
 
537
- promises.push(
538
- inject("vellum:platform_organization_id", params.organizationId),
539
- );
540
-
541
- if (params.userId) {
542
- promises.push(inject("vellum:platform_user_id", params.userId));
543
- }
544
-
545
533
  if (params.webhookSecret) {
546
534
  promises.push(inject("vellum:webhook_secret", params.webhookSecret));
547
535
  }
@@ -77,6 +77,7 @@ const PROVIDER_LABELS: Record<LlmProviderId, string> = {
77
77
  opencode: "OpenCode",
78
78
  baseten: "Baseten",
79
79
  poolside: "Poolside",
80
+ typesafe: "TypeSafe",
80
81
  };
81
82
 
82
83
  // litellm and opencode are deliberately excluded: they have no fixed default
@@ -246,6 +247,9 @@ export function inferProviderFromModel(model: string): string | undefined {
246
247
  if (model.startsWith("poolside/")) {
247
248
  return "poolside";
248
249
  }
250
+ if (model === "jev-latest" || model.startsWith("jev-")) {
251
+ return "typesafe";
252
+ }
249
253
  if (model === "qwen/qwen3-8b") {
250
254
  // The only qwen/* ID the Vellum hosted provider carries; the rest are
251
255
  // OpenRouter's.
@@ -187,6 +187,7 @@ export const DOCKER_STATEFUL_SET_SPEC: DockerStatefulSetSpec = {
187
187
  { kind: "static", name: "VELLUM_BACKUP_DIR", value: "/workspace/.backups" },
188
188
  { kind: "static", name: "VELLUM_BACKUP_KEY_PATH", value: "/workspace/.backup.key" },
189
189
  { kind: "static", name: "CES_CREDENTIAL_URL", value: "http://localhost:8090" },
190
+ { kind: "static", name: "CES_BOOTSTRAP_SOCKET_DIR", value: "/run/ces-bootstrap" },
190
191
  { kind: "static", name: "GATEWAY_IPC_SOCKET_DIR", value: "/run/gateway-ipc" },
191
192
  { kind: "static", name: "ASSISTANT_IPC_SOCKET_DIR", value: "/run/assistant-ipc" },
192
193
  { kind: "secret", name: "CES_SERVICE_TOKEN", secret: "cesServiceToken" },
@@ -34,6 +34,7 @@ export const LLM_PROVIDER_ENV_VAR_NAMES: Record<string, string> = {
34
34
  opencode: "OPENCODE_API_KEY",
35
35
  baseten: "BASETEN_API_KEY",
36
36
  poolside: "POOLSIDE_API_KEY",
37
+ typesafe: "TYPESAFE_API_KEY",
37
38
  };
38
39
 
39
40
  /** Search-provider env var names. Mirrors `SEARCH_PROVIDER_CATALOG` BYOK entries. */
@@ -44,6 +45,8 @@ export const SEARCH_PROVIDER_ENV_VAR_NAMES: Record<string, string> = {
44
45
  firecrawl: "FIRECRAWL_API_KEY",
45
46
  keenable: "KEENABLE_API_KEY",
46
47
  fastcrw: "FASTCRW_API_KEY",
48
+ searxng: "SEARXNG_API_KEY",
49
+ tinyfish: "TINYFISH_API_KEY",
47
50
  };
48
51
 
49
52
  /**