@vellumai/assistant 0.5.12 → 0.5.13

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 (31) hide show
  1. package/Dockerfile +41 -9
  2. package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
  3. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
  4. package/openapi.yaml +1 -1
  5. package/package.json +1 -1
  6. package/src/__tests__/first-greeting.test.ts +7 -0
  7. package/src/__tests__/navigate-settings-tab.test.ts +6 -2
  8. package/src/__tests__/platform.test.ts +3 -168
  9. package/src/__tests__/skill-feature-flags.test.ts +8 -0
  10. package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
  11. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
  12. package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
  13. package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
  14. package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
  15. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  16. package/src/config/bundled-skills/settings/TOOLS.json +5 -3
  17. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
  18. package/src/config/feature-flag-registry.json +1 -1
  19. package/src/credential-execution/client.ts +14 -2
  20. package/src/daemon/first-greeting.ts +6 -1
  21. package/src/daemon/lifecycle.ts +13 -8
  22. package/src/index.ts +0 -12
  23. package/src/memory/conversation-queries.ts +6 -6
  24. package/src/memory/journal-memory.ts +8 -2
  25. package/src/prompts/journal-context.ts +4 -1
  26. package/src/prompts/system-prompt.ts +11 -0
  27. package/src/runtime/http-server.ts +7 -15
  28. package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
  29. package/src/runtime/routes/secret-routes.ts +9 -2
  30. package/src/tools/browser/browser-manager.ts +2 -2
  31. package/src/util/platform.ts +1 -91
package/Dockerfile CHANGED
@@ -38,23 +38,55 @@ WORKDIR /app/assistant
38
38
 
39
39
  # Install runtime dependencies for Playwright and tree-sitter
40
40
  RUN apt-get update && apt-get install -y \
41
- curl \
42
- unzip \
41
+ bubblewrap \
43
42
  ca-certificates \
44
- python3 \
45
- libnss3 \
46
- libfreetype6 \
47
- libharfbuzz0b \
43
+ curl \
44
+ ffmpeg \
48
45
  fonts-freefont-ttf \
49
- make \
50
46
  g++ \
51
47
  git \
52
- sudo \
53
48
  htop \
54
- procps \
55
49
  jq \
50
+ libasound2 \
51
+ libatk-bridge2.0-0 \
52
+ libatk1.0-0 \
53
+ libatspi2.0-0 \
54
+ libcairo2 \
55
+ libcups2 \
56
+ libdrm2 \
57
+ libfreetype6 \
58
+ libgbm1 \
59
+ libharfbuzz0b \
60
+ libnspr4 \
61
+ libnss3 \
62
+ libpango-1.0-0 \
63
+ libpangocairo-1.0-0 \
64
+ libx11-6 \
65
+ libx11-xcb1 \
66
+ libxcb-dri3-0 \
67
+ libxcb1 \
68
+ libxcomposite1 \
69
+ libxdamage1 \
70
+ libxext6 \
71
+ libxfixes3 \
72
+ libxi6 \
73
+ libxkbcommon0 \
74
+ libxrandr2 \
75
+ libxrender1 \
76
+ libxshmfence1 \
77
+ libxtst6 \
78
+ lsof \
79
+ make \
80
+ openssl \
81
+ procps \
82
+ python3 \
83
+ sqlite3 \
84
+ sudo \
85
+ unzip \
56
86
  uuid-runtime \
57
87
  vim \
88
+ xclip \
89
+ xdg-utils \
58
90
  && rm -rf /var/lib/apt/lists/*
59
91
 
60
92
  # Copy bun binary from builder instead of re-installing
@@ -33,6 +33,13 @@ export const HandshakeRequestSchema = z.object({
33
33
  * can use it for platform credential materialisation.
34
34
  */
35
35
  assistantApiKey: z.string().optional(),
36
+ /**
37
+ * Optional platform assistant ID passed from the assistant runtime.
38
+ * In managed (sidecar) mode with warm pools, the PLATFORM_ASSISTANT_ID
39
+ * env var may not be set at CES startup. The assistant forwards it here
40
+ * so CES can use it for platform credential materialisation.
41
+ */
42
+ assistantId: z.string().optional(),
36
43
  });
37
44
  export type HandshakeRequest = z.infer<typeof HandshakeRequestSchema>;
38
45
 
@@ -422,6 +422,11 @@ export type ListAuditRecordsResponse = z.infer<
422
422
  export const UpdateManagedCredentialSchema = z.object({
423
423
  /** The assistant API key to push to CES for platform credential materialization. */
424
424
  assistantApiKey: z.string(), // nosemgrep: not-a-secret
425
+ /**
426
+ * Optional platform assistant ID. In warm-pool mode the ID may not be
427
+ * available at CES startup; the assistant pushes it here once provisioned.
428
+ */
429
+ assistantId: z.string().optional(),
425
430
  });
426
431
  export type UpdateManagedCredential = z.infer<
427
432
  typeof UpdateManagedCredentialSchema
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.0.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.5.11
6
+ version: 0.5.12
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -50,6 +50,13 @@ describe("first-greeting", () => {
50
50
  expect(isWakeUpGreeting("Wake Up, My Friend.", 0)).toBe(true);
51
51
  });
52
52
 
53
+ it("returns true for punctuation variations", () => {
54
+ writeFileSync(join(tempDir, "BOOTSTRAP.md"), "bootstrap content");
55
+ expect(isWakeUpGreeting("Wake up, my friend!", 0)).toBe(true);
56
+ expect(isWakeUpGreeting("Wake up, my friend?", 0)).toBe(true);
57
+ expect(isWakeUpGreeting("Wake up, my friend", 0)).toBe(true);
58
+ });
59
+
53
60
  it("returns false when content doesn't match wake-up greeting", () => {
54
61
  writeFileSync(join(tempDir, "BOOTSTRAP.md"), "bootstrap content");
55
62
  expect(isWakeUpGreeting("Hello", 0)).toBe(false);
@@ -9,11 +9,13 @@ function makeContext(sendToClient?: (msg: unknown) => void): ToolContext {
9
9
 
10
10
  const CANONICAL_TABS = [
11
11
  "General",
12
- "Channels",
13
12
  "Models & Services",
14
13
  "Voice",
14
+ "Sounds",
15
15
  "Permissions & Privacy",
16
- "Contacts",
16
+ "Billing",
17
+ "Archived Conversations",
18
+ "Schedules",
17
19
  "Developer",
18
20
  ];
19
21
 
@@ -28,6 +30,8 @@ const LEGACY_TABS = [
28
30
  "Privacy",
29
31
  "Sentry Testing",
30
32
  "Automation",
33
+ "Channels",
34
+ "Contacts",
31
35
  ];
32
36
 
33
37
  describe("navigate-settings-tab", () => {
@@ -1,15 +1,8 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { homedir, tmpdir } from "node:os";
2
+ import { existsSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { afterEach, describe, expect, mock, test } from "bun:test";
6
-
7
- // Mutable homedir override — when set, resolveInstanceDataDir reads from here.
8
- let homedirOverride: string | undefined;
9
- mock.module("node:os", () => ({
10
- homedir: () => homedirOverride ?? homedir(),
11
- tmpdir,
12
- }));
5
+ import { afterEach, describe, expect, test } from "bun:test";
13
6
 
14
7
  import {
15
8
  ensureDataDir,
@@ -28,7 +21,6 @@ import {
28
21
  getWorkspaceHooksDir,
29
22
  getWorkspacePromptPath,
30
23
  getWorkspaceSkillsDir,
31
- resolveInstanceDataDir,
32
24
  } from "../util/platform.js";
33
25
 
34
26
  const originalBaseDataDir = process.env.BASE_DATA_DIR;
@@ -39,7 +31,6 @@ afterEach(() => {
39
31
  } else {
40
32
  process.env.BASE_DATA_DIR = originalBaseDataDir;
41
33
  }
42
- homedirOverride = undefined;
43
34
  });
44
35
 
45
36
  // Baseline path characterization: documents current pre-migration path layout.
@@ -155,159 +146,3 @@ describe("workspace path primitives", () => {
155
146
  expect(getWorkspaceDir()).toBe("/tmp/custom-base/.vellum/workspace");
156
147
  });
157
148
  });
158
-
159
- describe("resolveInstanceDataDir", () => {
160
- function makeTempHome(): string {
161
- const dir = join(
162
- tmpdir(),
163
- `platform-home-${randomBytes(4).toString("hex")}`,
164
- );
165
- mkdirSync(dir, { recursive: true });
166
- homedirOverride = dir;
167
- return dir;
168
- }
169
-
170
- function writeLockfileToHome(
171
- home: string,
172
- data: Record<string, unknown>,
173
- ): void {
174
- writeFileSync(
175
- join(home, ".vellum.lock.json"),
176
- JSON.stringify(data, null, 2),
177
- );
178
- }
179
-
180
- test("returns undefined when no lockfile exists", () => {
181
- makeTempHome();
182
- expect(resolveInstanceDataDir()).toBeUndefined();
183
- });
184
-
185
- test("returns sole local assistant instanceDir when no activeAssistant", () => {
186
- const home = makeTempHome();
187
- writeLockfileToHome(home, {
188
- assistants: [
189
- {
190
- assistantId: "vellum-calm-stork",
191
- cloud: "local",
192
- resources: {
193
- instanceDir:
194
- "/Users/test/.local/share/vellum/assistants/vellum-calm-stork",
195
- },
196
- },
197
- ],
198
- });
199
- expect(resolveInstanceDataDir()).toBe(
200
- "/Users/test/.local/share/vellum/assistants/vellum-calm-stork",
201
- );
202
- });
203
-
204
- test("returns active assistant instanceDir when activeAssistant matches", () => {
205
- const home = makeTempHome();
206
- writeLockfileToHome(home, {
207
- activeAssistant: "vellum-bold-fox",
208
- assistants: [
209
- {
210
- assistantId: "vellum-calm-stork",
211
- cloud: "local",
212
- resources: {
213
- instanceDir:
214
- "/Users/test/.local/share/vellum/assistants/vellum-calm-stork",
215
- },
216
- },
217
- {
218
- assistantId: "vellum-bold-fox",
219
- cloud: "local",
220
- resources: {
221
- instanceDir:
222
- "/Users/test/.local/share/vellum/assistants/vellum-bold-fox",
223
- },
224
- },
225
- ],
226
- });
227
- expect(resolveInstanceDataDir()).toBe(
228
- "/Users/test/.local/share/vellum/assistants/vellum-bold-fox",
229
- );
230
- });
231
-
232
- test("returns undefined when multiple local assistants and no activeAssistant", () => {
233
- const home = makeTempHome();
234
- writeLockfileToHome(home, {
235
- assistants: [
236
- {
237
- assistantId: "vellum-calm-stork",
238
- cloud: "local",
239
- resources: {
240
- instanceDir:
241
- "/Users/test/.local/share/vellum/assistants/vellum-calm-stork",
242
- },
243
- },
244
- {
245
- assistantId: "vellum-bold-fox",
246
- cloud: "local",
247
- resources: {
248
- instanceDir:
249
- "/Users/test/.local/share/vellum/assistants/vellum-bold-fox",
250
- },
251
- },
252
- ],
253
- });
254
- expect(resolveInstanceDataDir()).toBeUndefined();
255
- });
256
-
257
- test("returns undefined when lockfile has no assistants array", () => {
258
- const home = makeTempHome();
259
- writeLockfileToHome(home, { version: 1 });
260
- expect(resolveInstanceDataDir()).toBeUndefined();
261
- });
262
-
263
- test("returns undefined when lockfile is malformed JSON", () => {
264
- const home = makeTempHome();
265
- writeFileSync(join(home, ".vellum.lock.json"), "{{not json");
266
- expect(resolveInstanceDataDir()).toBeUndefined();
267
- });
268
-
269
- test("treats assistants without cloud field as local", () => {
270
- const home = makeTempHome();
271
- writeLockfileToHome(home, {
272
- assistants: [
273
- {
274
- assistantId: "vellum-quiet-owl",
275
- resources: {
276
- instanceDir:
277
- "/Users/test/.local/share/vellum/assistants/vellum-quiet-owl",
278
- },
279
- },
280
- ],
281
- });
282
- expect(resolveInstanceDataDir()).toBe(
283
- "/Users/test/.local/share/vellum/assistants/vellum-quiet-owl",
284
- );
285
- });
286
-
287
- test("ignores cloud assistants when resolving", () => {
288
- const home = makeTempHome();
289
- writeLockfileToHome(home, {
290
- assistants: [
291
- {
292
- assistantId: "vellum-cloud-eagle",
293
- cloud: "platform",
294
- resources: {
295
- instanceDir: "/some/cloud/path",
296
- },
297
- },
298
- {
299
- assistantId: "vellum-local-robin",
300
- cloud: "local",
301
- resources: {
302
- instanceDir:
303
- "/Users/test/.local/share/vellum/assistants/vellum-local-robin",
304
- },
305
- },
306
- ],
307
- });
308
- // Only one local assistant, so it auto-selects
309
- expect(resolveInstanceDataDir()).toBe(
310
- "/Users/test/.local/share/vellum/assistants/vellum-local-robin",
311
- );
312
- });
313
- });
@@ -19,6 +19,7 @@ afterEach(() => {
19
19
  const DECLARED_FLAG_ID = "contacts";
20
20
  const DECLARED_FLAG_KEY = DECLARED_FLAG_ID;
21
21
  const DECLARED_SKILL_ID = "contacts";
22
+ const APP_BUILDER_MULTIFILE_FLAG_KEY = "app-builder-multifile";
22
23
  // ---------------------------------------------------------------------------
23
24
  // Helpers
24
25
  // ---------------------------------------------------------------------------
@@ -158,6 +159,13 @@ describe("isAssistantFeatureFlagEnabled", () => {
158
159
  // browser is declared in the registry with defaultEnabled: true
159
160
  expect(isAssistantFeatureFlagEnabled("browser", config)).toBe(true);
160
161
  });
162
+
163
+ test("app-builder-multifile defaults to enabled when no override is set", () => {
164
+ const config = makeConfig();
165
+ expect(
166
+ isAssistantFeatureFlagEnabled(APP_BUILDER_MULTIFILE_FLAG_KEY, config),
167
+ ).toBe(true);
168
+ });
161
169
  });
162
170
 
163
171
  // ---------------------------------------------------------------------------
@@ -0,0 +1,212 @@
1
+ import { execSync } from "node:child_process";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ /**
5
+ * Guard test: SKILL.md files must never instruct the assistant to accept
6
+ * secrets (passwords, API keys, tokens, etc.) pasted directly in chat.
7
+ *
8
+ * Secrets must always be collected via `credential_store prompt`, which
9
+ * presents a secure native UI that keeps the value out of conversation
10
+ * history and LLM context.
11
+ *
12
+ * This guard prevents regressions like the gmail/messaging bundled skill
13
+ * violation where SKILL.md contained "Include client_secret too if they
14
+ * provide one" — directing the assistant to accept a secret value from
15
+ * the chat stream.
16
+ */
17
+
18
+ /** SKILL.md files permitted to contain otherwise-violating patterns. */
19
+ const ALLOWLIST = new Set<string>([
20
+ // Add paths here only if there is a genuine, documented exception.
21
+ ]);
22
+
23
+ /**
24
+ * Words that indicate the line is about a secret/credential value.
25
+ */
26
+ const SECRET_WORDS =
27
+ "secret|password|api[_\\s-]?key|auth[_\\s-]?token|private[_\\s-]?key|access[_\\s-]?token|client[_\\s-]?secret|signing[_\\s-]?key|bearer[_\\s-]?token";
28
+
29
+ /**
30
+ * Patterns that indicate the assistant is being told to accept a secret
31
+ * value directly in chat, rather than via credential_store prompt.
32
+ */
33
+ const VIOLATION_PATTERNS: RegExp[] = [
34
+ // "accept <secret> in/via/from chat/plaintext/the conversation"
35
+ new RegExp(
36
+ `accept\\s+.*(?:${SECRET_WORDS}).*\\b(?:in|via|from)\\s+(?:chat|plaintext|the\\s+conversation)`,
37
+ "i",
38
+ ),
39
+ new RegExp(
40
+ `accept\\s+.*\\b(?:in|via|from)\\s+(?:chat|plaintext|the\\s+conversation).*(?:${SECRET_WORDS})`,
41
+ "i",
42
+ ),
43
+ // "ask (the user|them) (for|to share/send/paste/type/provide) <secret>" where destination is chat
44
+ // Must have "the user" or "them" as the object to avoid matching third-party descriptions
45
+ // like "Discord will ask for a 2FA code before revealing the secret"
46
+ new RegExp(
47
+ `ask\\s+(?:the\\s+user|them)\\s+(?:for|to\\s+(?:share|send|paste|type|provide))\\s+(?:the\\s+|their\\s+|a\\s+)?(?:${SECRET_WORDS})`,
48
+ "i",
49
+ ),
50
+ // "Include <secret> too if they provide one" — the original gmail violation pattern
51
+ new RegExp(
52
+ `include\\s+(?:the\\s+)?(?:${SECRET_WORDS})\\s+(?:too|as\\s+well|also)\\s+if\\s+they\\s+provide`,
53
+ "i",
54
+ ),
55
+ // "<secret> pasted/typed/sent in chat/conversation/plaintext"
56
+ new RegExp(
57
+ `(?:${SECRET_WORDS})\\s+(?:pasted|typed|sent|provided|shared)\\s+(?:in|via|through)\\s+(?:chat|conversation|plaintext)`,
58
+ "i",
59
+ ),
60
+ // "paste/type/send <secret> in chat/here/the conversation"
61
+ new RegExp(
62
+ `(?:paste|type|send|share|provide)\\s+(?:the\\s+|your\\s+|their\\s+)?(?:${SECRET_WORDS})\\s+(?:in\\s+(?:chat|the\\s+conversation)|here)`,
63
+ "i",
64
+ ),
65
+ ];
66
+
67
+ /**
68
+ * Lines containing these negation words are typically instructing the
69
+ * assistant NOT to do something — these are not violations.
70
+ */
71
+ const NEGATION_PATTERNS =
72
+ /\b(?:never|do\s+not|don['']t|must\s+not|should\s+not|shouldn['']t)\b|\bNOT\b/;
73
+
74
+ /**
75
+ * Lines that are YAML-style field values within a credential_store prompt
76
+ * block (label, description, placeholder). These contain secret-related
77
+ * words but are secure UI text, not chat instructions.
78
+ */
79
+ const CREDENTIAL_STORE_UI_FIELD =
80
+ /^\s*(?:[-*]\s+)?(?:label|description|placeholder)\s*[:=]\s*/i;
81
+
82
+ interface Violation {
83
+ file: string;
84
+ line: number;
85
+ text: string;
86
+ }
87
+
88
+ function findViolations(): Violation[] {
89
+ const repoRoot = process.cwd() + "/..";
90
+
91
+ // Find all SKILL.md files tracked by git
92
+ let skillFiles: string[];
93
+ try {
94
+ const output = execSync(`git grep -l "" -- '*/SKILL.md'`, {
95
+ encoding: "utf-8",
96
+ cwd: repoRoot,
97
+ }).trim();
98
+ skillFiles = output.split("\n").filter((f) => f.length > 0);
99
+ } catch (err) {
100
+ if ((err as { status?: number }).status === 1) {
101
+ return []; // no SKILL.md files found
102
+ }
103
+ throw err;
104
+ }
105
+
106
+ // Filter to skills/ and assistant/src/config/bundled-skills/ directories
107
+ skillFiles = skillFiles.filter(
108
+ (f) =>
109
+ f.startsWith("skills/") ||
110
+ f.startsWith("assistant/src/config/bundled-skills/"),
111
+ );
112
+
113
+ const violations: Violation[] = [];
114
+
115
+ for (const filePath of skillFiles) {
116
+ if (ALLOWLIST.has(filePath)) continue;
117
+
118
+ let content: string;
119
+ try {
120
+ content = execSync(`git show HEAD:${filePath}`, {
121
+ encoding: "utf-8",
122
+ cwd: repoRoot,
123
+ });
124
+ } catch {
125
+ continue;
126
+ }
127
+
128
+ const lines = content.split("\n");
129
+
130
+ // Track whether we're inside a credential_store prompt block
131
+ // (indented YAML-like content after a credential_store mention)
132
+ let inCredentialStoreBlock = false;
133
+ let blockIndent = 0;
134
+
135
+ for (let i = 0; i < lines.length; i++) {
136
+ const line = lines[i];
137
+ const lineNumber = i + 1;
138
+
139
+ // Track credential_store prompt blocks
140
+ if (/credential_store\s+prompt/i.test(line)) {
141
+ inCredentialStoreBlock = true;
142
+ blockIndent = line.search(/\S/);
143
+ continue;
144
+ }
145
+
146
+ // Exit credential_store block when indentation returns to same or lesser level
147
+ if (inCredentialStoreBlock) {
148
+ const currentIndent = line.search(/\S/);
149
+ if (
150
+ currentIndent !== -1 &&
151
+ currentIndent <= blockIndent &&
152
+ line.trim().length > 0
153
+ ) {
154
+ inCredentialStoreBlock = false;
155
+ }
156
+ }
157
+
158
+ // Skip empty lines
159
+ if (line.trim().length === 0) continue;
160
+
161
+ // Skip negation lines — these instruct NOT to do something
162
+ if (NEGATION_PATTERNS.test(line)) continue;
163
+
164
+ // Skip credential_store UI field lines (label:, description:, placeholder:)
165
+ if (inCredentialStoreBlock && CREDENTIAL_STORE_UI_FIELD.test(line))
166
+ continue;
167
+
168
+ // Strip markdown backticks before pattern matching so that
169
+ // violations like `client_secret` are caught the same as bare words.
170
+ const stripped = line.replace(/`/g, "");
171
+
172
+ // Check against violation patterns
173
+ for (const pattern of VIOLATION_PATTERNS) {
174
+ if (pattern.test(stripped)) {
175
+ violations.push({
176
+ file: filePath,
177
+ line: lineNumber,
178
+ text: line.trim(),
179
+ });
180
+ break; // one violation per line is enough
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ return violations;
187
+ }
188
+
189
+ describe("SKILL.md secret handling guard", () => {
190
+ test("no SKILL.md files instruct accepting secrets in chat", () => {
191
+ const violations = findViolations();
192
+
193
+ if (violations.length > 0) {
194
+ const message = [
195
+ "Found SKILL.md files that instruct accepting secrets directly in chat.",
196
+ "Secrets must always be collected via `credential_store prompt`, which",
197
+ "presents a secure native UI that keeps values out of conversation history.",
198
+ "",
199
+ "Violations:",
200
+ ...violations.map((v) => ` - ${v.file}:${v.line}: ${v.text}`),
201
+ "",
202
+ "To fix: replace chat-based secret collection with a `credential_store prompt` call.",
203
+ "See any *-setup skill (e.g. skills/slack-app-setup/SKILL.md) for the correct pattern.",
204
+ "",
205
+ "If this is a genuine exception, add the file path to the ALLOWLIST in",
206
+ "skill-secret-handling-guard.test.ts.",
207
+ ].join("\n");
208
+
209
+ expect(violations, message).toEqual([]);
210
+ }
211
+ });
212
+ });
@@ -205,7 +205,7 @@ function makeSystemPrompt(size: "small" | "production" = "small"): string {
205
205
  "### OAuth Setup",
206
206
  "Most integrations use OAuth for authentication.",
207
207
  "Guide the user through the OAuth flow when setting up a new integration:",
208
- "1. Navigate to Settings > Integrations",
208
+ "1. Navigate to Settings > Models & Services",
209
209
  "2. Click 'Connect' for the desired service",
210
210
  "3. Authorize in the browser popup",
211
211
  "4. Confirm the connection is active",