@vellumai/assistant 0.5.15 → 0.5.16

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 (175) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/architecture/integrations.md +15 -14
  3. package/knip.json +3 -1
  4. package/openapi.yaml +11 -43
  5. package/package.json +1 -1
  6. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -375
  7. package/src/__tests__/ces-rpc-credential-backend.test.ts +4 -1
  8. package/src/__tests__/checker.test.ts +59 -0
  9. package/src/__tests__/cli-command-risk-guard.test.ts +98 -10
  10. package/src/__tests__/cli-memory.test.ts +372 -0
  11. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
  12. package/src/__tests__/config-schema.test.ts +0 -2
  13. package/src/__tests__/config-watcher-feature-flags.test.ts +211 -0
  14. package/src/__tests__/conversation-runtime-assembly.test.ts +7 -4
  15. package/src/__tests__/conversation-slash-commands.test.ts +2 -6
  16. package/src/__tests__/conversation-usage.test.ts +1 -0
  17. package/src/__tests__/credential-security-e2e.test.ts +4 -1
  18. package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
  19. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
  20. package/src/__tests__/guardian-routing-invariants.test.ts +151 -0
  21. package/src/__tests__/heartbeat-service.test.ts +1 -3
  22. package/src/__tests__/intent-routing.test.ts +6 -18
  23. package/src/__tests__/log-export-workspace.test.ts +2 -28
  24. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
  25. package/src/__tests__/managed-store.test.ts +2 -10
  26. package/src/__tests__/messaging-send-tool.test.ts +6 -6
  27. package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
  28. package/src/__tests__/migration-export-http.test.ts +3 -34
  29. package/src/__tests__/migration-import-commit-http.test.ts +1 -29
  30. package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
  31. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
  32. package/src/__tests__/oauth-apps-routes.test.ts +120 -10
  33. package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
  34. package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
  35. package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
  36. package/src/__tests__/oauth-providers-routes.test.ts +5 -2
  37. package/src/__tests__/oauth-store.test.ts +0 -5
  38. package/src/__tests__/outlook-messaging-provider.test.ts +576 -0
  39. package/src/__tests__/path-policy.test.ts +2 -17
  40. package/src/__tests__/permission-types.test.ts +0 -1
  41. package/src/__tests__/platform-callback-registration.test.ts +3 -7
  42. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  43. package/src/__tests__/provider-error-scenarios.test.ts +0 -2
  44. package/src/__tests__/qdrant-manager.test.ts +68 -21
  45. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  46. package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
  47. package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
  48. package/src/__tests__/secret-allowlist.test.ts +20 -35
  49. package/src/__tests__/shell-credential-ref.test.ts +0 -5
  50. package/src/__tests__/skill-load-feature-flag.test.ts +2 -43
  51. package/src/__tests__/skill-load-inline-command.test.ts +3 -65
  52. package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
  53. package/src/__tests__/skill-load-tool.test.ts +3 -67
  54. package/src/__tests__/skill-memory.test.ts +362 -119
  55. package/src/__tests__/skills.test.ts +22 -49
  56. package/src/__tests__/slack-channel-config.test.ts +2 -21
  57. package/src/__tests__/starter-bundle.test.ts +2 -8
  58. package/src/__tests__/stt-hints.test.ts +7 -2
  59. package/src/__tests__/system-prompt.test.ts +25 -45
  60. package/src/__tests__/task-compiler.test.ts +0 -21
  61. package/src/__tests__/task-management-tools.test.ts +0 -21
  62. package/src/__tests__/task-memory-cleanup.test.ts +0 -21
  63. package/src/__tests__/task-runner.test.ts +0 -21
  64. package/src/__tests__/task-scheduler.test.ts +0 -21
  65. package/src/__tests__/terminal-tools.test.ts +1 -17
  66. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
  67. package/src/__tests__/tool-approval-handler.test.ts +1 -20
  68. package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
  69. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
  70. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  71. package/src/__tests__/tool-executor.test.ts +0 -1
  72. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -20
  73. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
  74. package/src/__tests__/trust-store.test.ts +9 -41
  75. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
  76. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -21
  77. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -22
  78. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -22
  79. package/src/__tests__/trusted-contact-verification.test.ts +0 -22
  80. package/src/__tests__/turn-boundary-resolution.test.ts +0 -28
  81. package/src/__tests__/twilio-provider.test.ts +0 -16
  82. package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
  83. package/src/__tests__/twilio-routes.test.ts +0 -24
  84. package/src/__tests__/update-bulletin.test.ts +17 -89
  85. package/src/__tests__/usage-cache-backfill-migration.test.ts +0 -20
  86. package/src/__tests__/usage-routes.test.ts +0 -21
  87. package/src/__tests__/user-reference.test.ts +1 -5
  88. package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
  89. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
  90. package/src/__tests__/voice-invite-redemption.test.ts +0 -21
  91. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -24
  92. package/src/__tests__/voice-session-bridge.test.ts +0 -21
  93. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -23
  94. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -23
  96. package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
  97. package/src/acp/client-handler.ts +1 -2
  98. package/src/cli/__tests__/notifications.test.ts +0 -22
  99. package/src/cli/cli-memory.ts +176 -0
  100. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  101. package/src/cli/commands/oauth/connect.ts +15 -0
  102. package/src/cli/commands/oauth/providers.ts +49 -42
  103. package/src/cli/commands/platform/__tests__/connect.test.ts +2 -48
  104. package/src/cli/commands/platform/__tests__/disconnect.test.ts +2 -48
  105. package/src/cli/commands/platform/__tests__/status.test.ts +0 -50
  106. package/src/config/bundled-skills/computer-use/TOOLS.json +7 -7
  107. package/src/config/bundled-skills/messaging/SKILL.md +17 -2
  108. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  109. package/src/config/feature-flag-registry.json +16 -0
  110. package/src/config/loader.ts +4 -0
  111. package/src/config/schemas/security.ts +0 -6
  112. package/src/config/schemas/services.ts +8 -0
  113. package/src/context/window-manager.ts +28 -9
  114. package/src/credential-execution/approval-bridge.ts +0 -1
  115. package/src/daemon/config-watcher.ts +51 -0
  116. package/src/daemon/conversation-agent-loop.ts +3 -2
  117. package/src/daemon/conversation-process.ts +1 -0
  118. package/src/daemon/conversation-usage.ts +1 -0
  119. package/src/daemon/handlers/skills.ts +9 -1
  120. package/src/daemon/lifecycle.ts +13 -4
  121. package/src/daemon/message-types/conversations.ts +1 -0
  122. package/src/daemon/providers-setup.ts +2 -0
  123. package/src/daemon/server.ts +26 -22
  124. package/src/events/domain-events.ts +1 -2
  125. package/src/memory/db-init.ts +9 -0
  126. package/src/memory/job-handlers/batch-extraction.ts +16 -4
  127. package/src/memory/job-handlers/embedding.test.ts +3 -27
  128. package/src/memory/job-handlers/journal-carry-forward.test.ts +1 -29
  129. package/src/memory/llm-usage-store.ts +35 -2
  130. package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
  131. package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -0
  132. package/src/memory/migrations/index.ts +2 -0
  133. package/src/memory/qdrant-manager.ts +26 -5
  134. package/src/memory/query-expansion.ts +1 -1
  135. package/src/memory/retriever.test.ts +22 -20
  136. package/src/memory/retriever.ts +10 -2
  137. package/src/memory/schema/oauth.ts +1 -1
  138. package/src/memory/search/mmr.ts +8 -5
  139. package/src/memory/slack-thread-store.ts +17 -0
  140. package/src/messaging/providers/outlook/adapter.ts +193 -0
  141. package/src/messaging/providers/outlook/client.ts +311 -0
  142. package/src/messaging/providers/outlook/types.ts +83 -0
  143. package/src/notifications/adapters/slack.ts +1 -1
  144. package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
  145. package/src/oauth/connect-orchestrator.ts +10 -3
  146. package/src/oauth/oauth-store.ts +10 -11
  147. package/src/oauth/provider-serializer.ts +3 -0
  148. package/src/oauth/provider-visibility.ts +16 -0
  149. package/src/oauth/seed-providers.ts +49 -17
  150. package/src/permissions/checker.ts +39 -7
  151. package/src/permissions/types.ts +2 -4
  152. package/src/prompts/journal-context.ts +9 -11
  153. package/src/prompts/system-prompt.ts +3 -64
  154. package/src/prompts/templates/UPDATES.md +6 -0
  155. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -27
  156. package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
  157. package/src/runtime/auth/route-policy.ts +0 -4
  158. package/src/runtime/guardian-reply-router.ts +6 -2
  159. package/src/runtime/routes/conversation-query-routes.ts +2 -58
  160. package/src/runtime/routes/inbound-stages/background-dispatch.ts +43 -2
  161. package/src/runtime/routes/memory-item-routes.test.ts +0 -17
  162. package/src/runtime/routes/memory-item-routes.ts +103 -12
  163. package/src/runtime/routes/oauth-apps.ts +18 -1
  164. package/src/runtime/routes/oauth-providers.ts +13 -1
  165. package/src/runtime/routes/settings-routes.ts +1 -0
  166. package/src/runtime/routes/usage-routes.ts +19 -2
  167. package/src/runtime/routes/work-items-routes.test.ts +0 -21
  168. package/src/runtime/routes/workspace-routes.test.ts +3 -27
  169. package/src/security/secret-allowlist.ts +4 -4
  170. package/src/skills/skill-memory.ts +62 -23
  171. package/src/tools/memory/handlers.test.ts +1 -29
  172. package/src/tools/permission-checker.ts +0 -18
  173. package/src/tools/skills/skill-script-runner.ts +1 -1
  174. package/src/util/device-id.ts +3 -65
  175. package/src/workspace/git-service.ts +27 -6
@@ -15,7 +15,6 @@ import {
15
15
  mkdtempSync,
16
16
  readdirSync,
17
17
  readFileSync,
18
- realpathSync,
19
18
  rmSync,
20
19
  symlinkSync,
21
20
  writeFileSync,
@@ -25,29 +24,9 @@ import { join } from "node:path";
25
24
  import { afterAll, describe, expect, mock, test } from "bun:test";
26
25
 
27
26
  // Set up temp directories before mocking
28
- const testDir = realpathSync(
29
- mkdtempSync(join(tmpdir(), "log-export-workspace-test-")),
30
- );
31
- const testWorkspaceDir = join(testDir, "workspace");
32
- const testDbDir = join(testDir, "db");
33
- const testDbPath = join(testDbDir, "assistant.db");
34
-
27
+ const testDir = process.env.VELLUM_WORKSPACE_DIR!;
28
+ const testWorkspaceDir = testDir;
35
29
  mkdirSync(testWorkspaceDir, { recursive: true });
36
- mkdirSync(testDbDir, { recursive: true });
37
-
38
- mock.module("../util/platform.js", () => ({
39
- getProtectedDir: () => join(testDir, "protected"),
40
- getDataDir: () => testDir,
41
- getWorkspaceDir: () => testWorkspaceDir,
42
- getWorkspaceConfigPath: () => join(testWorkspaceDir, "config.json"),
43
- isMacOS: () => process.platform === "darwin",
44
- isLinux: () => process.platform === "linux",
45
- isWindows: () => process.platform === "win32",
46
- getPidPath: () => join(testDir, "test.pid"),
47
- getDbPath: () => testDbPath,
48
- getLogPath: () => join(testDir, "test.log"),
49
- ensureDataDir: () => {},
50
- }));
51
30
 
52
31
  mock.module("../util/logger.js", () => ({
53
32
  getLogger: () =>
@@ -68,11 +47,6 @@ initializeDb();
68
47
 
69
48
  afterAll(() => {
70
49
  resetDb();
71
- try {
72
- rmSync(testDir, { recursive: true });
73
- } catch {
74
- /* best effort */
75
- }
76
50
  });
77
51
 
78
52
  // ---------------------------------------------------------------------------
@@ -1,8 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
- import { mkdtempSync } from "node:fs";
3
- import { tmpdir } from "node:os";
1
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
4
2
  import { join } from "node:path";
5
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
3
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
6
4
 
7
5
  let TEST_DIR = "";
8
6
 
@@ -38,22 +36,6 @@ const mockConfig = {
38
36
  },
39
37
  };
40
38
 
41
- mock.module("../util/platform.js", () => ({
42
- getProtectedDir: () => join(TEST_DIR, "protected"),
43
- getWorkspaceSkillsDir: () => join(TEST_DIR, "skills"),
44
- getDataDir: () => TEST_DIR,
45
- ensureDataDir: () => {},
46
- getPidPath: () => join(TEST_DIR, "vellum.pid"),
47
- getDbPath: () => join(TEST_DIR, "data", "assistant.db"),
48
- getLogPath: () => join(TEST_DIR, "logs", "vellum.log"),
49
- getHistoryPath: () => join(TEST_DIR, "history"),
50
- isMacOS: () => process.platform === "darwin",
51
- isLinux: () => process.platform === "linux",
52
- isWindows: () => process.platform === "win32",
53
- getPlatformName: () => process.platform,
54
- getClipboardCommand: () => null,
55
- }));
56
-
57
39
  mock.module("../util/logger.js", () => ({
58
40
  getLogger: () =>
59
41
  new Proxy({} as Record<string, unknown>, {
@@ -81,7 +63,6 @@ mock.module("../tools/terminal/sandbox.js", () => ({
81
63
  }));
82
64
 
83
65
  import { loadSkillCatalog } from "../config/skills.js";
84
- import { buildSystemPrompt } from "../prompts/system-prompt.js";
85
66
  import { executeDeleteManagedSkill } from "../tools/skills/delete-managed.js";
86
67
  import { SkillLoadTool } from "../tools/skills/load.js";
87
68
  import { executeScaffoldManagedSkill } from "../tools/skills/scaffold-managed.js";
@@ -96,14 +77,10 @@ function makeContext(): ToolContext {
96
77
  }
97
78
 
98
79
  beforeEach(() => {
99
- TEST_DIR = mkdtempSync(join(tmpdir(), "lifecycle-test-"));
80
+ TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
100
81
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
101
82
  });
102
83
 
103
- afterEach(() => {
104
- rmSync(TEST_DIR, { recursive: true, force: true });
105
- });
106
-
107
84
  describe("managed skill lifecycle: scaffold → catalog → prompt → delete", () => {
108
85
  test("full lifecycle: create skill, verify in catalog and prompt, then delete", async () => {
109
86
  // Step 1: Scaffold a managed skill
@@ -137,14 +114,7 @@ describe("managed skill lifecycle: scaffold → catalog → prompt → delete",
137
114
  expect(found!.name).toBe("Lifecycle Test");
138
115
  expect(found!.description).toBe("Integration test skill.");
139
116
 
140
- // Step 4: Verify skill appears in system prompt (markdown bullet: **id**: description)
141
- const prompt = buildSystemPrompt();
142
- expect(prompt).toContain("**lifecycle-test**");
143
- expect(prompt).toContain("Integration test skill");
144
- // Dynamic Skill Authoring section moved to tool descriptions; prompt should not contain it
145
- expect(prompt).not.toContain("## Dynamic Skill Authoring Workflow");
146
-
147
- // Step 5: Delete the skill
117
+ // Step 4: Delete the skill
148
118
  const deleteResult = await executeDeleteManagedSkill(
149
119
  {
150
120
  skill_id: "lifecycle-test",
@@ -156,14 +126,14 @@ describe("managed skill lifecycle: scaffold → catalog → prompt → delete",
156
126
  const deleteData = JSON.parse(deleteResult.content as string);
157
127
  expect(deleteData.deleted).toBe(true);
158
128
 
159
- // Step 6: Verify skill is gone from filesystem
129
+ // Step 5: Verify skill is gone from filesystem
160
130
  expect(existsSync(skillMdPath)).toBe(false);
161
131
 
162
- // Step 7: Verify skill no longer in catalog
132
+ // Step 6: Verify skill no longer in catalog
163
133
  const catalogAfter = loadSkillCatalog();
164
134
  expect(catalogAfter.find((s) => s.id === "lifecycle-test")).toBeUndefined();
165
135
 
166
- // Step 8: Verify SKILLS.md index no longer has the entry
136
+ // Step 7: Verify SKILLS.md index no longer has the entry
167
137
  const indexPath = join(TEST_DIR, "skills", "SKILLS.md");
168
138
  if (existsSync(indexPath)) {
169
139
  const indexContent = readFileSync(indexPath, "utf-8");
@@ -7,8 +7,6 @@ import {
7
7
  rmSync,
8
8
  writeFileSync,
9
9
  } from "node:fs";
10
- import { mkdtempSync } from "node:fs";
11
- import { tmpdir } from "node:os";
12
10
  import { join } from "node:path";
13
11
  import {
14
12
  afterEach,
@@ -22,12 +20,7 @@ import {
22
20
 
23
21
  import { parse as parseYaml } from "yaml";
24
22
 
25
- let TEST_DIR = "";
26
-
27
- mock.module("../util/platform.js", () => ({
28
- getProtectedDir: () => join(TEST_DIR, "protected"),
29
- getWorkspaceSkillsDir: () => join(TEST_DIR, "skills"),
30
- }));
23
+ const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
31
24
 
32
25
  mock.module("../util/logger.js", () => ({
33
26
  getLogger: () =>
@@ -48,12 +41,11 @@ import {
48
41
  } from "../skills/managed-store.js";
49
42
 
50
43
  beforeEach(() => {
51
- TEST_DIR = mkdtempSync(join(tmpdir(), "managed-store-test-"));
52
44
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
53
45
  });
54
46
 
55
47
  afterEach(() => {
56
- rmSync(TEST_DIR, { recursive: true, force: true });
48
+ rmSync(join(TEST_DIR, "skills"), { recursive: true, force: true });
57
49
  });
58
50
 
59
51
  describe("validateManagedSkillId", () => {
@@ -64,16 +64,16 @@ const getConversationMock = mock(
64
64
  );
65
65
 
66
66
  const syncMessageToDiskMock = mock(
67
- (
68
- _conversationId: string,
69
- _messageId: string,
70
- _createdAtMs: number,
71
- ) => {},
67
+ (_conversationId: string, _messageId: string, _createdAtMs: number) => {},
72
68
  );
73
69
 
74
70
  const getBindingByChannelChatMock = mock(
75
71
  (_sourceChannel: string, _externalChatId: string) =>
76
- null as { conversationId: string; sourceChannel: string; externalChatId: string } | null,
72
+ null as {
73
+ conversationId: string;
74
+ sourceChannel: string;
75
+ externalChatId: string;
76
+ } | null,
77
77
  );
78
78
 
79
79
  mock.module("../memory/conversation-crud.js", () => ({
@@ -20,19 +20,15 @@
20
20
  import { createHash } from "node:crypto";
21
21
  import {
22
22
  mkdirSync,
23
- mkdtempSync,
24
23
  readdirSync,
25
24
  readFileSync,
26
- realpathSync,
27
25
  rmSync,
28
26
  unlinkSync,
29
27
  writeFileSync,
30
28
  } from "node:fs";
31
- import { tmpdir } from "node:os";
32
29
  import { join } from "node:path";
33
30
  import { gzipSync } from "node:zlib";
34
31
  import {
35
- afterAll,
36
32
  afterEach,
37
33
  beforeAll,
38
34
  beforeEach,
@@ -50,27 +46,11 @@ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
50
46
  ) as ArrayBuffer;
51
47
  }
52
48
 
53
- const testDir = realpathSync(
54
- mkdtempSync(join(tmpdir(), "migration-cross-version-test-")),
55
- );
49
+ const testDir = process.env.VELLUM_WORKSPACE_DIR!;
56
50
  const testDbDir = join(testDir, "data", "db");
57
51
  const testDbPath = join(testDbDir, "assistant.db");
58
52
  const testConfigPath = join(testDir, "config.json");
59
53
 
60
- mock.module("../util/platform.js", () => ({
61
- getProtectedDir: () => join(testDir, "protected"),
62
- getDataDir: () => join(testDir, "data"),
63
- getWorkspaceDir: () => testDir,
64
- getWorkspaceConfigPath: () => testConfigPath,
65
- isMacOS: () => process.platform === "darwin",
66
- isLinux: () => process.platform === "linux",
67
- isWindows: () => process.platform === "win32",
68
- getPidPath: () => join(testDir, "test.pid"),
69
- getDbPath: () => testDbPath,
70
- getLogPath: () => join(testDir, "test.log"),
71
- ensureDataDir: () => {},
72
- }));
73
-
74
54
  mock.module("../util/logger.js", () => ({
75
55
  getLogger: () =>
76
56
  new Proxy({} as Record<string, unknown>, {
@@ -137,14 +117,6 @@ beforeAll(() => {
137
117
  writeFileSync(testConfigPath, JSON.stringify(EXISTING_CONFIG, null, 2));
138
118
  });
139
119
 
140
- afterAll(() => {
141
- try {
142
- rmSync(testDir, { recursive: true });
143
- } catch {
144
- /* best effort */
145
- }
146
- });
147
-
148
120
  // Restore test files before each test
149
121
  beforeEach(() => {
150
122
  mkdirSync(testDbDir, { recursive: true });
@@ -12,39 +12,16 @@
12
12
  * - Integration: existing routes are unaffected by the new endpoint
13
13
  */
14
14
  import { createHash } from "node:crypto";
15
- import {
16
- mkdirSync,
17
- mkdtempSync,
18
- realpathSync,
19
- rmSync,
20
- writeFileSync,
21
- } from "node:fs";
22
- import { tmpdir } from "node:os";
15
+ import { mkdirSync, writeFileSync } from "node:fs";
23
16
  import { join } from "node:path";
24
17
  import { gunzipSync } from "node:zlib";
25
- import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
18
+ import { beforeAll, describe, expect, mock, test } from "bun:test";
26
19
 
27
- const testDir = realpathSync(
28
- mkdtempSync(join(tmpdir(), "migration-export-http-test-")),
29
- );
20
+ const testDir = process.env.VELLUM_WORKSPACE_DIR!;
30
21
  const testDbDir = join(testDir, "data", "db");
31
22
  const testDbPath = join(testDbDir, "assistant.db");
32
23
  const testConfigPath = join(testDir, "config.json");
33
24
 
34
- mock.module("../util/platform.js", () => ({
35
- getProtectedDir: () => join(testDir, "protected"),
36
- getDataDir: () => join(testDir, "data"),
37
- getWorkspaceDir: () => testDir,
38
- getWorkspaceConfigPath: () => testConfigPath,
39
- isMacOS: () => process.platform === "darwin",
40
- isLinux: () => process.platform === "linux",
41
- isWindows: () => process.platform === "win32",
42
- getPidPath: () => join(testDir, "test.pid"),
43
- getDbPath: () => testDbPath,
44
- getLogPath: () => join(testDir, "test.log"),
45
- ensureDataDir: () => {},
46
- }));
47
-
48
25
  mock.module("../util/logger.js", () => ({
49
26
  getLogger: () =>
50
27
  new Proxy({} as Record<string, unknown>, {
@@ -101,14 +78,6 @@ beforeAll(() => {
101
78
  writeFileSync(testConfigPath, JSON.stringify(TEST_CONFIG, null, 2));
102
79
  });
103
80
 
104
- afterAll(() => {
105
- try {
106
- rmSync(testDir, { recursive: true });
107
- } catch {
108
- /* best effort */
109
- }
110
- });
111
-
112
81
  // ---------------------------------------------------------------------------
113
82
  // Tar parsing helper (mirrors vbundle-validator's internal parser)
114
83
  // ---------------------------------------------------------------------------
@@ -15,19 +15,15 @@ import { createHash } from "node:crypto";
15
15
  import {
16
16
  existsSync,
17
17
  mkdirSync,
18
- mkdtempSync,
19
18
  readdirSync,
20
19
  readFileSync,
21
- realpathSync,
22
20
  rmSync,
23
21
  unlinkSync,
24
22
  writeFileSync,
25
23
  } from "node:fs";
26
- import { tmpdir } from "node:os";
27
24
  import { join } from "node:path";
28
25
  import { gzipSync } from "node:zlib";
29
26
  import {
30
- afterAll,
31
27
  afterEach,
32
28
  beforeAll,
33
29
  beforeEach,
@@ -45,27 +41,11 @@ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
45
41
  ) as ArrayBuffer;
46
42
  }
47
43
 
48
- const testDir = realpathSync(
49
- mkdtempSync(join(tmpdir(), "migration-import-commit-http-test-")),
50
- );
44
+ const testDir = process.env.VELLUM_WORKSPACE_DIR!;
51
45
  const testDbDir = join(testDir, "data", "db");
52
46
  const testDbPath = join(testDbDir, "assistant.db");
53
47
  const testConfigPath = join(testDir, "config.json");
54
48
 
55
- mock.module("../util/platform.js", () => ({
56
- getProtectedDir: () => join(testDir, "protected"),
57
- getDataDir: () => join(testDir, "data"),
58
- getWorkspaceDir: () => testDir,
59
- getWorkspaceConfigPath: () => testConfigPath,
60
- isMacOS: () => process.platform === "darwin",
61
- isLinux: () => process.platform === "linux",
62
- isWindows: () => process.platform === "win32",
63
- getPidPath: () => join(testDir, "test.pid"),
64
- getDbPath: () => testDbPath,
65
- getLogPath: () => join(testDir, "test.log"),
66
- ensureDataDir: () => {},
67
- }));
68
-
69
49
  mock.module("../util/logger.js", () => ({
70
50
  getLogger: () =>
71
51
  new Proxy({} as Record<string, unknown>, {
@@ -119,14 +99,6 @@ beforeAll(() => {
119
99
  writeFileSync(testConfigPath, JSON.stringify(EXISTING_CONFIG, null, 2));
120
100
  });
121
101
 
122
- afterAll(() => {
123
- try {
124
- rmSync(testDir, { recursive: true });
125
- } catch {
126
- /* best effort */
127
- }
128
- });
129
-
130
102
  // Restore test files before each test so mutations from previous tests
131
103
  // do not leak across test cases.
132
104
  beforeEach(() => {
@@ -11,17 +11,10 @@
11
11
  * - Integration: existing routes are unaffected by the new endpoint
12
12
  */
13
13
  import { createHash } from "node:crypto";
14
- import {
15
- mkdirSync,
16
- mkdtempSync,
17
- realpathSync,
18
- rmSync,
19
- writeFileSync,
20
- } from "node:fs";
21
- import { tmpdir } from "node:os";
14
+ import { mkdirSync, writeFileSync } from "node:fs";
22
15
  import { join } from "node:path";
23
16
  import { gzipSync } from "node:zlib";
24
- import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
17
+ import { beforeAll, describe, expect, mock, test } from "bun:test";
25
18
 
26
19
  /** Convert a Uint8Array to an ArrayBuffer for BodyInit compatibility. */
27
20
  function toArrayBuffer(data: Uint8Array): ArrayBuffer {
@@ -31,27 +24,11 @@ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
31
24
  ) as ArrayBuffer;
32
25
  }
33
26
 
34
- const testDir = realpathSync(
35
- mkdtempSync(join(tmpdir(), "migration-import-preflight-http-test-")),
36
- );
27
+ const testDir = process.env.VELLUM_WORKSPACE_DIR!;
37
28
  const testDbDir = join(testDir, "data", "db");
38
29
  const testDbPath = join(testDbDir, "assistant.db");
39
30
  const testConfigPath = join(testDir, "config.json");
40
31
 
41
- mock.module("../util/platform.js", () => ({
42
- getProtectedDir: () => join(testDir, "protected"),
43
- getDataDir: () => join(testDir, "data"),
44
- getWorkspaceDir: () => testDir,
45
- getWorkspaceConfigPath: () => testConfigPath,
46
- isMacOS: () => process.platform === "darwin",
47
- isLinux: () => process.platform === "linux",
48
- isWindows: () => process.platform === "win32",
49
- getPidPath: () => join(testDir, "test.pid"),
50
- getDbPath: () => testDbPath,
51
- getLogPath: () => join(testDir, "test.log"),
52
- ensureDataDir: () => {},
53
- }));
54
-
55
32
  mock.module("../util/logger.js", () => ({
56
33
  getLogger: () =>
57
34
  new Proxy({} as Record<string, unknown>, {
@@ -107,14 +84,6 @@ beforeAll(() => {
107
84
  writeFileSync(testConfigPath, JSON.stringify(EXISTING_CONFIG, null, 2));
108
85
  });
109
86
 
110
- afterAll(() => {
111
- try {
112
- rmSync(testDir, { recursive: true });
113
- } catch {
114
- /* best effort */
115
- }
116
- });
117
-
118
87
  // ---------------------------------------------------------------------------
119
88
  // Tar archive builder helpers (mirrors validate test)
120
89
  // ---------------------------------------------------------------------------
@@ -5,7 +5,8 @@ import { describe, expect, test } from "bun:test";
5
5
  /**
6
6
  * Guard test: domain-specific routing sections must NOT appear in the system
7
7
  * prompt source file. Routing cues now live in skill frontmatter
8
- * (`activation-hints` / `avoid-when`) and are projected into the skills catalog.
8
+ * (`activation-hints` / `avoid-when`) and are seeded as capability memories
9
+ * for semantic discovery.
9
10
  *
10
11
  * If this test fails, you are re-introducing hardcoded routing into the system
11
12
  * prompt. Instead, add `activation-hints` to the skill's SKILL.md frontmatter.
@@ -55,7 +55,6 @@ mock.module("../oauth/oauth-store.js", () => ({
55
55
  defaultScopes: "[]",
56
56
  scopePolicy: "[]",
57
57
  extraParams: null,
58
- callbackTransport: null,
59
58
  pingUrl: null,
60
59
  pingMethod: null,
61
60
  pingHeaders: null,
@@ -70,6 +69,7 @@ mock.module("../oauth/oauth-store.js", () => ({
70
69
  identityBody: null,
71
70
  identityFormat: null,
72
71
  identityOkField: null,
72
+ featureFlag: null,
73
73
  createdAt: 1735689500000,
74
74
  updatedAt: 1735689550000,
75
75
  }
@@ -88,16 +88,18 @@ mock.module("../oauth/oauth-store.js", () => ({
88
88
  ),
89
89
  }));
90
90
 
91
+ const mockOrchestrateOAuthConnect = mock(() =>
92
+ Promise.resolve({
93
+ success: true,
94
+ deferred: false,
95
+ grantedScopes: [],
96
+ accountInfo: null,
97
+ refreshTokenPresent: false,
98
+ }),
99
+ );
100
+
91
101
  mock.module("../oauth/connect-orchestrator.js", () => ({
92
- orchestrateOAuthConnect: mock(() =>
93
- Promise.resolve({
94
- success: true,
95
- deferred: false,
96
- grantedScopes: [],
97
- accountInfo: null,
98
- refreshTokenPresent: false,
99
- }),
100
- ),
102
+ orchestrateOAuthConnect: mockOrchestrateOAuthConnect,
101
103
  }));
102
104
 
103
105
  import { oauthAppsRouteDefinitions } from "../runtime/routes/oauth-apps.js";
@@ -202,3 +204,111 @@ describe("GET /v1/oauth/apps", () => {
202
204
  expect(body.provider).toBeNull();
203
205
  });
204
206
  });
207
+
208
+ describe("POST /v1/oauth/apps/:appId/connect — callback_transport", () => {
209
+ function connectRequest(body?: unknown) {
210
+ return new Request("http://localhost/v1/oauth/apps/app-1/connect", {
211
+ method: "POST",
212
+ headers: { "Content-Type": "application/json" },
213
+ body: body !== undefined ? JSON.stringify(body) : undefined,
214
+ });
215
+ }
216
+
217
+ test('callback_transport: "gateway" is accepted and passed through', async () => {
218
+ mockOrchestrateOAuthConnect.mockClear();
219
+ const req = connectRequest({ callback_transport: "gateway" });
220
+ const url = new URL(req.url);
221
+ const res = await getRoute("POST", "oauth/apps/:appId/connect").handler({
222
+ req,
223
+ url,
224
+ server: null as never,
225
+ authContext: null as never,
226
+ params: { appId: "app-1" },
227
+ });
228
+
229
+ expect(res.status).toBe(200);
230
+ expect(mockOrchestrateOAuthConnect).toHaveBeenCalledTimes(1);
231
+ const callArgs = (
232
+ mockOrchestrateOAuthConnect.mock.calls as unknown as Array<
233
+ [{ callbackTransport: string }]
234
+ >
235
+ )[0]![0];
236
+ expect(callArgs.callbackTransport).toBe("gateway");
237
+ });
238
+
239
+ test('callback_transport: "loopback" is accepted and passed through', async () => {
240
+ mockOrchestrateOAuthConnect.mockClear();
241
+ const req = connectRequest({ callback_transport: "loopback" });
242
+ const url = new URL(req.url);
243
+ const res = await getRoute("POST", "oauth/apps/:appId/connect").handler({
244
+ req,
245
+ url,
246
+ server: null as never,
247
+ authContext: null as never,
248
+ params: { appId: "app-1" },
249
+ });
250
+
251
+ expect(res.status).toBe(200);
252
+ expect(mockOrchestrateOAuthConnect).toHaveBeenCalledTimes(1);
253
+ const callArgs = (
254
+ mockOrchestrateOAuthConnect.mock.calls as unknown as Array<
255
+ [{ callbackTransport: string }]
256
+ >
257
+ )[0]![0];
258
+ expect(callArgs.callbackTransport).toBe("loopback");
259
+ });
260
+
261
+ test('omitting callback_transport defaults to "loopback"', async () => {
262
+ mockOrchestrateOAuthConnect.mockClear();
263
+ const req = connectRequest({ scopes: ["email"] });
264
+ const url = new URL(req.url);
265
+ const res = await getRoute("POST", "oauth/apps/:appId/connect").handler({
266
+ req,
267
+ url,
268
+ server: null as never,
269
+ authContext: null as never,
270
+ params: { appId: "app-1" },
271
+ });
272
+
273
+ expect(res.status).toBe(200);
274
+ expect(mockOrchestrateOAuthConnect).toHaveBeenCalledTimes(1);
275
+ const callArgs = (
276
+ mockOrchestrateOAuthConnect.mock.calls as unknown as Array<
277
+ [{ callbackTransport: string }]
278
+ >
279
+ )[0]![0];
280
+ expect(callArgs.callbackTransport).toBe("loopback");
281
+ });
282
+
283
+ test('invalid callback_transport "websocket" returns 400', async () => {
284
+ mockOrchestrateOAuthConnect.mockClear();
285
+ const req = connectRequest({ callback_transport: "websocket" });
286
+ const url = new URL(req.url);
287
+ const res = await getRoute("POST", "oauth/apps/:appId/connect").handler({
288
+ req,
289
+ url,
290
+ server: null as never,
291
+ authContext: null as never,
292
+ params: { appId: "app-1" },
293
+ });
294
+
295
+ expect(res.status).toBe(400);
296
+ expect(mockOrchestrateOAuthConnect).not.toHaveBeenCalled();
297
+ });
298
+
299
+ test("invalid callback_transport (number) returns 400", async () => {
300
+ mockOrchestrateOAuthConnect.mockClear();
301
+ const req = connectRequest({ callback_transport: 123 });
302
+ const url = new URL(req.url);
303
+ const res = await getRoute("POST", "oauth/apps/:appId/connect").handler({
304
+ req,
305
+ url,
306
+ server: null as never,
307
+ authContext: null as never,
308
+ params: { appId: "app-1" },
309
+ });
310
+
311
+ expect(res.status).toBe(400);
312
+ expect(mockOrchestrateOAuthConnect).not.toHaveBeenCalled();
313
+ });
314
+ });