@vellumai/assistant 0.5.4 → 0.5.5

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 (59) hide show
  1. package/Dockerfile +18 -27
  2. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -0
  3. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +42 -0
  4. package/package.json +1 -1
  5. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  6. package/src/__tests__/openai-whisper.test.ts +93 -0
  7. package/src/__tests__/slack-messaging-token-resolution.test.ts +319 -0
  8. package/src/__tests__/volume-security-guard.test.ts +155 -0
  9. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -0
  10. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +16 -37
  11. package/src/config/env-registry.ts +9 -0
  12. package/src/config/feature-flag-registry.json +8 -0
  13. package/src/credential-execution/managed-catalog.ts +5 -15
  14. package/src/daemon/config-watcher.ts +4 -1
  15. package/src/daemon/daemon-control.ts +7 -0
  16. package/src/daemon/lifecycle.ts +7 -1
  17. package/src/daemon/providers-setup.ts +2 -1
  18. package/src/hooks/manager.ts +7 -0
  19. package/src/instrument.ts +33 -1
  20. package/src/memory/embedding-local.ts +11 -5
  21. package/src/memory/job-handlers/conversation-starters.ts +24 -36
  22. package/src/messaging/provider.ts +9 -0
  23. package/src/messaging/providers/slack/adapter.ts +29 -2
  24. package/src/oauth/connection-resolver.test.ts +22 -18
  25. package/src/oauth/connection-resolver.ts +92 -7
  26. package/src/oauth/platform-connection.test.ts +78 -69
  27. package/src/oauth/platform-connection.ts +12 -19
  28. package/src/permissions/trust-client.ts +343 -0
  29. package/src/permissions/trust-store-interface.ts +105 -0
  30. package/src/permissions/trust-store.ts +523 -36
  31. package/src/platform/client.test.ts +148 -0
  32. package/src/platform/client.ts +71 -0
  33. package/src/providers/speech-to-text/openai-whisper.test.ts +190 -0
  34. package/src/providers/speech-to-text/openai-whisper.ts +68 -0
  35. package/src/providers/speech-to-text/resolve.ts +9 -0
  36. package/src/providers/speech-to-text/types.ts +17 -0
  37. package/src/runtime/http-server.ts +2 -2
  38. package/src/runtime/routes/inbound-message-handler.ts +27 -3
  39. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -1
  40. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +287 -0
  41. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +122 -0
  42. package/src/runtime/routes/log-export-routes.ts +1 -0
  43. package/src/runtime/routes/secret-routes.ts +4 -1
  44. package/src/security/ces-credential-client.ts +173 -0
  45. package/src/security/secure-keys.ts +65 -22
  46. package/src/signals/bash.ts +3 -0
  47. package/src/signals/cancel.ts +3 -0
  48. package/src/signals/confirm.ts +3 -0
  49. package/src/signals/conversation-undo.ts +3 -0
  50. package/src/signals/event-stream.ts +7 -0
  51. package/src/signals/shotgun.ts +3 -0
  52. package/src/signals/trust-rule.ts +3 -0
  53. package/src/telemetry/usage-telemetry-reporter.test.ts +23 -36
  54. package/src/telemetry/usage-telemetry-reporter.ts +21 -19
  55. package/src/util/device-id.ts +70 -7
  56. package/src/util/logger.ts +35 -9
  57. package/src/util/platform.ts +29 -5
  58. package/src/workspace/migrations/migrate-to-workspace-volume.ts +113 -0
  59. package/src/workspace/migrations/registry.ts +2 -0
@@ -8,7 +8,11 @@ import {
8
8
  import { homedir } from "node:os";
9
9
  import { join } from "node:path";
10
10
 
11
- import { getBaseDataDir } from "../config/env-registry.js";
11
+ import {
12
+ getBaseDataDir,
13
+ getIsContainerized,
14
+ getWorkspaceDirOverride,
15
+ } from "../config/env-registry.js";
12
16
 
13
17
  export function isMacOS(): boolean {
14
18
  return process.platform === "darwin";
@@ -237,6 +241,15 @@ export function getInterfacesDir(): string {
237
241
  return join(getDataDir(), "interfaces");
238
242
  }
239
243
 
244
+ /**
245
+ * Returns the sounds directory (~/.vellum/workspace/data/sounds).
246
+ * Custom sound files and sound configuration live here. Sound files
247
+ * can be large, so this directory is excluded from diagnostic exports.
248
+ */
249
+ export function getSoundsDir(): string {
250
+ return join(getWorkspaceDir(), "data", "sounds");
251
+ }
252
+
240
253
  /**
241
254
  * Returns the TCP port the daemon should listen on for iOS clients.
242
255
  * Hardcoded default: 8765.
@@ -356,13 +369,19 @@ export function getSignalsDir(): string {
356
369
  // Currently not used by call-sites; wired in later PRs.
357
370
 
358
371
  /**
359
- * Returns ~/.vellum/workspace — the workspace root for user-facing state.
372
+ * Returns the workspace root for user-facing state.
373
+ *
374
+ * When the WORKSPACE_DIR env var is set, returns that value (used in
375
+ * containerized deployments where the workspace is a separate volume).
376
+ * Otherwise falls back to ~/.vellum/workspace.
360
377
  *
361
378
  * WARNING: The entire workspace directory is included in diagnostic log exports
362
379
  * ("Send logs to Vellum"). Do not store secrets, API keys, or sensitive
363
380
  * credentials here — use the credential store or ~/.vellum/protected/ instead.
364
381
  */
365
382
  export function getWorkspaceDir(): string {
383
+ const override = getWorkspaceDirOverride();
384
+ if (override) return override;
366
385
  return join(getRootDir(), "workspace");
367
386
  }
368
387
 
@@ -413,13 +432,17 @@ export function ensureDataDir(): void {
413
432
  const root = getRootDir();
414
433
  const workspace = getWorkspaceDir();
415
434
  const wsData = join(workspace, "data");
435
+ const containerized = getIsContainerized();
416
436
  const dirs = [
417
- // Root-level dirs (runtime / protected)
437
+ // Root-level dirs (runtime)
418
438
  root,
419
- join(root, "protected"),
439
+ // protected, signals, hooks are local-only — skip in containerized mode
440
+ // (credentials via CES HTTP API, trust via gateway API, no IPC signals)
441
+ ...(containerized
442
+ ? []
443
+ : [join(root, "protected"), join(root, "signals"), join(root, "hooks")]),
420
444
  // Workspace dirs
421
445
  workspace,
422
- join(root, "hooks"),
423
446
  join(workspace, "skills"),
424
447
  join(workspace, "embedding-models"),
425
448
  join(workspace, "conversations"),
@@ -432,6 +455,7 @@ export function ensureDataDir(): void {
432
455
  join(wsData, "memory", "knowledge"),
433
456
  join(wsData, "apps"),
434
457
  join(wsData, "interfaces"),
458
+ join(wsData, "sounds"),
435
459
  ];
436
460
  for (const dir of dirs) {
437
461
  if (!existsSync(dir)) {
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Workspace migration: Migrate workspace data from /data to /workspace volume.
3
+ *
4
+ * In the old Docker volume layout, workspace data lived at
5
+ * `$BASE_DATA_DIR/.vellum/workspace`. In the new layout, WORKSPACE_DIR points
6
+ * to a dedicated volume (e.g. `/workspace`). On first boot with the new layout,
7
+ * this migration copies existing workspace data from the old location to the
8
+ * new volume so nothing is lost.
9
+ *
10
+ * Idempotent:
11
+ * - Skips if WORKSPACE_DIR is not set (non-Docker or old layout).
12
+ * - Skips if the workspace volume already has data (config.json exists).
13
+ * - Skips if the sentinel file exists (already migrated).
14
+ * - Skips if the old workspace directory doesn't exist or is empty.
15
+ */
16
+
17
+ import { cpSync, existsSync, readdirSync, writeFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+
20
+ import {
21
+ getBaseDataDir,
22
+ getWorkspaceDirOverride,
23
+ } from "../../config/env-registry.js";
24
+ import type { WorkspaceMigration } from "./types.js";
25
+
26
+ const SENTINEL_FILENAME = ".workspace-volume-migrated";
27
+
28
+ export const migrateToWorkspaceVolumeMigration: WorkspaceMigration = {
29
+ id: "014-migrate-to-workspace-volume",
30
+ description:
31
+ "Copy workspace data from old /data/.vellum/workspace to new WORKSPACE_DIR volume on first boot",
32
+
33
+ run(workspaceDir: string): void {
34
+ const workspaceDirOverride = getWorkspaceDirOverride();
35
+
36
+ // Only relevant when WORKSPACE_DIR is explicitly set (Docker with separate volume)
37
+ if (!workspaceDirOverride) return;
38
+
39
+ const sentinelPath = join(workspaceDir, SENTINEL_FILENAME);
40
+
41
+ // Already migrated — skip
42
+ if (existsSync(sentinelPath)) return;
43
+
44
+ // If the workspace volume already has data (config.json), assume it's
45
+ // already populated — either by a previous migration or manual setup.
46
+ if (existsSync(join(workspaceDir, "config.json"))) {
47
+ // Write sentinel so we don't re-check on every boot
48
+ writeSentinel(sentinelPath);
49
+ return;
50
+ }
51
+
52
+ // Resolve the old workspace location: $BASE_DATA_DIR/.vellum/workspace
53
+ const baseDataDir = getBaseDataDir();
54
+ if (!baseDataDir) {
55
+ // No BASE_DATA_DIR means there's no old location to migrate from
56
+ writeSentinel(sentinelPath);
57
+ return;
58
+ }
59
+
60
+ const oldWorkspaceDir = join(baseDataDir, ".vellum", "workspace");
61
+
62
+ // If the old workspace doesn't exist or is empty, nothing to migrate
63
+ if (!existsSync(oldWorkspaceDir)) {
64
+ writeSentinel(sentinelPath);
65
+ return;
66
+ }
67
+
68
+ let entries: string[];
69
+ try {
70
+ entries = readdirSync(oldWorkspaceDir);
71
+ } catch {
72
+ // Can't read old workspace — write sentinel and move on
73
+ writeSentinel(sentinelPath);
74
+ return;
75
+ }
76
+
77
+ if (entries.length === 0) {
78
+ writeSentinel(sentinelPath);
79
+ return;
80
+ }
81
+
82
+ // Copy everything from old workspace to new workspace volume.
83
+ // Use cpSync with recursive to handle nested directories.
84
+ // Copy each entry individually rather than the whole directory to avoid
85
+ // overwriting the target directory itself (which may already have
86
+ // sub-directories created by ensureDataDir).
87
+ for (const entry of entries) {
88
+ const src = join(oldWorkspaceDir, entry);
89
+ const dst = join(workspaceDir, entry);
90
+
91
+ // Skip if destination already exists (partial previous run)
92
+ if (existsSync(dst)) continue;
93
+
94
+ try {
95
+ cpSync(src, dst, { recursive: true });
96
+ } catch {
97
+ // Best-effort per entry — continue with remaining items
98
+ }
99
+ }
100
+
101
+ // Mark migration complete
102
+ writeSentinel(sentinelPath);
103
+ },
104
+ };
105
+
106
+ function writeSentinel(sentinelPath: string): void {
107
+ try {
108
+ writeFileSync(sentinelPath, new Date().toISOString() + "\n", "utf-8");
109
+ } catch {
110
+ // Best-effort — if we can't write the sentinel, the migration runner's
111
+ // checkpoint will still prevent re-running the migration function.
112
+ }
113
+ }
@@ -10,6 +10,7 @@ import { appDirRenameMigration } from "./010-app-dir-rename.js";
10
10
  import { backfillInstallationIdMigration } from "./011-backfill-installation-id.js";
11
11
  import { renameConversationDiskViewDirsMigration } from "./012-rename-conversation-disk-view-dirs.js";
12
12
  import { repairConversationDiskViewMigration } from "./013-repair-conversation-disk-view.js";
13
+ import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
13
14
  import type { WorkspaceMigration } from "./types.js";
14
15
 
15
16
  /**
@@ -29,4 +30,5 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
29
30
  appDirRenameMigration,
30
31
  renameConversationDiskViewDirsMigration,
31
32
  repairConversationDiskViewMigration,
33
+ migrateToWorkspaceVolumeMigration,
32
34
  ];