@vellumai/assistant 0.12.2-dev.202609171913.b5e95d3 → 0.12.2-dev.202609172017.3da279e

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.
@@ -127,7 +127,7 @@ export async function buildSlackUserLabelMap(
127
127
  ids.map(async (id): Promise<[string, string] | undefined> => {
128
128
  try {
129
129
  const label = await resolveLabel(id);
130
- const sanitized = sanitizeOptionalLabel(label ?? undefined);
130
+ const sanitized = sanitizeSlackLabel(label ?? undefined);
131
131
  if (!sanitized || sanitized === id) return undefined;
132
132
  return [id, sanitized];
133
133
  } catch {
@@ -166,7 +166,7 @@ export async function buildSlackChannelLabelMap(
166
166
  ids.map(async (id): Promise<[string, string] | undefined> => {
167
167
  try {
168
168
  const label = await resolveLabel(id);
169
- const sanitized = sanitizeOptionalLabel(label ?? undefined);
169
+ const sanitized = sanitizeSlackLabel(label ?? undefined);
170
170
  if (!sanitized || sanitized === id) return undefined;
171
171
  return [id, sanitized];
172
172
  } catch {
@@ -222,7 +222,7 @@ function renderChannelReference(
222
222
  return `#${embeddedLabel}`;
223
223
  }
224
224
 
225
- const resolvedLabel = sanitizeOptionalLabel(
225
+ const resolvedLabel = sanitizeSlackLabel(
226
226
  options.channelLabels?.[channelId],
227
227
  );
228
228
  if (resolvedLabel && resolvedLabel !== channelId) {
@@ -320,15 +320,11 @@ export function sanitizeSlackLabel(
320
320
  function sanitizeEmbeddedSlackLabel(
321
321
  label: string | undefined,
322
322
  ): string | undefined {
323
- return sanitizeOptionalLabel(
323
+ return sanitizeSlackLabel(
324
324
  label === undefined ? undefined : decodeSlackHtmlEntities(label),
325
325
  );
326
326
  }
327
327
 
328
- function sanitizeOptionalLabel(label: string | undefined): string | undefined {
329
- return sanitizeSlackLabel(label);
330
- }
331
-
332
328
  function isSlackUserId(value: string): boolean {
333
329
  return /^[UW][A-Z0-9]+$/.test(value);
334
330
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.12.2-dev.202609171913.b5e95d3",
3
+ "version": "0.12.2-dev.202609172017.3da279e",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -338,7 +338,7 @@ describe("POST /v1/btw", () => {
338
338
  expect(options!.config!.modelIntent).toBeUndefined();
339
339
  });
340
340
 
341
- test("greeting requests pass callSite: 'emptyStateGreeting'", async () => {
341
+ test("greeting requests pass callSite: 'emptyStateGreeting' and send no tools", async () => {
342
342
  const provider = makeMockProvider();
343
343
  const session = makeMockSession(provider);
344
344
  mockGetOrCreateConversation.mockImplementationOnce(async () => session);
@@ -352,6 +352,9 @@ describe("POST /v1/btw", () => {
352
352
  expect(provider.sendMessage).toHaveBeenCalledTimes(1);
353
353
  const [, options] = provider.sendMessage.mock.calls[0];
354
354
  expect(options!.config!.callSite).toBe("emptyStateGreeting");
355
+ // The greeting targets no real conversation, so there is no cache prefix
356
+ // for tool definitions to share; they would only cost tokens.
357
+ expect(options!.tools).toEqual([]);
355
358
  });
356
359
 
357
360
  test("greeting requests include fresh turn context using the client timezone", async () => {
@@ -673,6 +673,35 @@ describe("injectChannelCapabilityContext", () => {
673
673
  const text = (result.content[0] as { type: "text"; text: string }).text;
674
674
  expect(text).not.toContain("Do NOT use markdown tables");
675
675
  });
676
+
677
+ test("injects email send CLI constraint for email channel", () => {
678
+ const caps: ChannelCapabilities = {
679
+ channel: "email",
680
+ dashboardCapable: false,
681
+ supportsDynamicUi: false,
682
+ supportsVoiceInput: false,
683
+ };
684
+
685
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
686
+ const text = (result.content[0] as { type: "text"; text: string }).text;
687
+ expect(text).toContain("Conversation text is not emailed");
688
+ expect(text).toContain("assistant email send");
689
+ expect(text).toContain("--reply-to");
690
+ });
691
+
692
+ test("does NOT inject email send CLI constraint for non-email channels", () => {
693
+ const caps: ChannelCapabilities = {
694
+ channel: "telegram",
695
+ dashboardCapable: false,
696
+ supportsDynamicUi: false,
697
+ supportsVoiceInput: false,
698
+ };
699
+
700
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
701
+ const text = (result.content[0] as { type: "text"; text: string }).text;
702
+ expect(text).not.toContain("Conversation text is not emailed");
703
+ expect(text).not.toContain("assistant email send");
704
+ });
676
705
  });
677
706
 
678
707
  // ---------------------------------------------------------------------------
@@ -12,10 +12,11 @@ import { describe, expect, test } from "bun:test";
12
12
  import { MemoryRetrospectiveConfigSchema } from "../schemas/memory-retrospective.js";
13
13
 
14
14
  describe("memory.retrospective config schema", () => {
15
- test("an empty block leaves retrospectives and skill improvement on", () => {
15
+ test("an empty block leaves skill improvement on and monitoring off", () => {
16
16
  const parsed = MemoryRetrospectiveConfigSchema.parse({});
17
17
  expect(parsed.enabled).toBe(true);
18
18
  expect(parsed.skillImprovement).toBe(true);
19
+ expect(parsed.skillImprovementMonitoring).toBe(false);
19
20
  expect(parsed).not.toHaveProperty("forkStrategy");
20
21
  });
21
22
 
@@ -41,6 +42,18 @@ describe("memory.retrospective config schema", () => {
41
42
  ).toBe(false);
42
43
  });
43
44
 
45
+ test("skillImprovementMonitoring is a boolean-only opt-in", () => {
46
+ const parsed = MemoryRetrospectiveConfigSchema.parse({
47
+ skillImprovementMonitoring: true,
48
+ });
49
+ expect(parsed.skillImprovementMonitoring).toBe(true);
50
+ expect(
51
+ MemoryRetrospectiveConfigSchema.safeParse({
52
+ skillImprovementMonitoring: "true",
53
+ }).success,
54
+ ).toBe(false);
55
+ });
56
+
44
57
  test("a leftover forkStrategy key is ignored", () => {
45
58
  const parsed = MemoryRetrospectiveConfigSchema.parse({
46
59
  forkStrategy: "cloning",
@@ -165,11 +165,7 @@
165
165
  },
166
166
  "description": "Replacement steps (replaces all existing steps)"
167
167
  }
168
- },
169
- "oneOf": [
170
- { "required": ["id"] },
171
- { "required": ["enrollment_id", "enrollment_action"] }
172
- ]
168
+ }
173
169
  },
174
170
  "executor": "tools/sequence-update.ts",
175
171
  "execution_target": "host"
@@ -11,14 +11,23 @@ export const MemoryRetrospectiveConfigSchema = z
11
11
 
12
12
  skillImprovement: z
13
13
  .boolean({
14
- error:
15
- "memory.retrospective.skillImprovement must be a boolean",
14
+ error: "memory.retrospective.skillImprovement must be a boolean",
16
15
  })
17
16
  .default(true)
18
17
  .describe(
19
18
  "Whether retrospectives may discover, refine, and create managed skills from observed procedures. When false, retrospectives still capture ordinary memories through `remember`, but cannot load skill management, search for similar skills, or scaffold managed skills.",
20
19
  ),
21
20
 
21
+ skillImprovementMonitoring: z
22
+ .boolean({
23
+ error:
24
+ "memory.retrospective.skillImprovementMonitoring must be a boolean",
25
+ })
26
+ .default(false)
27
+ .describe(
28
+ "Reserved opt-in for monitoring retrospective skill-improvement decisions. This setting currently has no effect.",
29
+ ),
30
+
22
31
  timeThresholdMs: z
23
32
  .number({
24
33
  error: "memory.retrospective.timeThresholdMs must be a number",
@@ -903,6 +903,11 @@ export function buildChannelCapabilityBlock(
903
903
  "- Do NOT use markdown tables — use bullet lists instead. No markdown headers — use **bold** or CAPS for emphasis.",
904
904
  );
905
905
  }
906
+ if (caps.channel === "email") {
907
+ lines.push(
908
+ "- Conversation text is not emailed. To reply, run `assistant email send` (see `assistant email send --help`). Use `--reply-to` to keep the thread. Skip a reply only when none is needed.",
909
+ );
910
+ }
906
911
  }
907
912
 
908
913
  // Inject group chat etiquette only when the chat type indicates a multi-party
@@ -2035,20 +2040,6 @@ export async function composeInjectorChain(ctx: TurnContext): Promise<string> {
2035
2040
  */
2036
2041
  const DEFAULT_PLACEMENT: InjectionPlacement = "append-user-tail";
2037
2042
 
2038
- /**
2039
- * Count leading memory-prefix blocks on a user message's `content`.
2040
- *
2041
- * Delegates to {@link countMemoryPrefixBlocks} from
2042
- * `memory/graph/conversation-graph-memory.js` — the canonical state-machine
2043
- * for locating the memory-prefix boundary. Reusing it here keeps the
2044
- * PKB-context / PKB-reminder / NOW splice rules aligned on a single source
2045
- * of truth so their ordering relative to any memory prefix is stable and
2046
- * testable.
2047
- */
2048
- function countMemoryPrefixBlocksOnContent(content: ContentBlock[]): number {
2049
- return countMemoryPrefixBlocks(content);
2050
- }
2051
-
2052
2043
  /**
2053
2044
  * Apply one injector block to a `runMessages` array according to its
2054
2045
  * declared {@link InjectionPlacement}:
@@ -2101,9 +2092,7 @@ function applyInjectionBlock(
2101
2092
  { ...userTail, content: [...userTail.content, textBlock] },
2102
2093
  ];
2103
2094
  case "after-memory-prefix": {
2104
- const memoryPrefixCount = countMemoryPrefixBlocksOnContent(
2105
- userTail.content,
2106
- );
2095
+ const memoryPrefixCount = countMemoryPrefixBlocks(userTail.content);
2107
2096
  return [
2108
2097
  ...runMessages.slice(0, -1),
2109
2098
  {
@@ -2161,7 +2150,7 @@ function stripTailV2DynamicMemoryPrefix(
2161
2150
  if (!last || last.role !== "user") {
2162
2151
  return messages;
2163
2152
  }
2164
- const prefixCount = countMemoryPrefixBlocksOnContent(last.content);
2153
+ const prefixCount = countMemoryPrefixBlocks(last.content);
2165
2154
  if (prefixCount === 0) {
2166
2155
  return messages;
2167
2156
  }
@@ -577,6 +577,12 @@ export function isProviderErrorMetadata(
577
577
  * assistant rows, and turn grouping closes on them, so display merging and
578
578
  * the turn resolver agree on boundaries. Takes the raw persisted `metadata`
579
579
  * JSON string; malformed JSON and non-assistant roles are never standalone.
580
+ *
581
+ * The web folds adjacent assistant rows again after pagination and reads the
582
+ * same rule off the wire projection in its own `isStandaloneAssistantMessage`
583
+ * (clients/web/src/domains/chat/utils/is-standalone-assistant-message.ts). A
584
+ * kind added here without a matching flag and check there merges on the
585
+ * client anyway.
580
586
  */
581
587
  export function isStandaloneAssistantMessage(
582
588
  role: string,
@@ -955,19 +955,6 @@ function likeContainsPattern(query: string): string {
955
955
  .replace(/_/g, "\\_")}%`;
956
956
  }
957
957
 
958
- /**
959
- * Whether the sparse Qdrant `messages_lexical` index — the only source of
960
- * message-content matches — is a safe read source. Content matching is
961
- * unavailable (title matches only) until the one-time upgrade backfill has
962
- * fully drained: a partially populated collection would silently miss older
963
- * content (an empty result — not a throw). Indexing itself is unconditional
964
- * host infrastructure, so completion is the only gate; the recall read site
965
- * applies the same one via the shared {@link isLexicalBackfillComplete}.
966
- */
967
- function isMessageContentSearchAvailable(): boolean {
968
- return isLexicalBackfillComplete();
969
- }
970
-
971
958
  /**
972
959
  * Full-text search across message content.
973
960
  *
@@ -976,9 +963,9 @@ function isMessageContentSearchAvailable(): boolean {
976
963
  * merged with a `LIKE` match on conversation titles; matching conversations
977
964
  * return with their relevant messages, ordered by most recently updated.
978
965
  *
979
- * Content matching is index-only — there is no `messages.content` scan
966
+ * Content matching is index-only: there is no `messages.content` scan
980
967
  * fallback and no other content source. Only the title arm can match while
981
- * the index is not a safe read source ({@link isMessageContentSearchAvailable}),
968
+ * the index is not a safe read source ({@link isLexicalBackfillComplete}),
982
969
  * for a query that tokenizes to nothing under the shared tokenizer (non-ASCII
983
970
  * or single-char input like "你", "é", "C++"), or when the Qdrant lexical
984
971
  * lookup fails (logged). An unindexed or unreachable index yields fewer
@@ -1017,7 +1004,7 @@ export async function searchConversations(
1017
1004
  const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
1018
1005
 
1019
1006
  const hasTokens = hasLexicalTokens(trimmed);
1020
- const contentSearchAvailable = isMessageContentSearchAvailable();
1007
+ const contentSearchAvailable = isLexicalBackfillComplete();
1021
1008
 
1022
1009
  // LIKE pattern for title matching (message-content indexes don't cover titles).
1023
1010
  const titlePattern = likeContainsPattern(query);
@@ -388,7 +388,7 @@ export async function routeGuardianReply(
388
388
  const request = await getGuardianRequestOrNull(answerTap.requestId);
389
389
  if (
390
390
  request &&
391
- resolveRequestInstructionMode(request) === "answer" &&
391
+ resolveGuardianInstructionModeForRequest(request) === "answer" &&
392
392
  parseQuestionAnswerActionId(answerTap.token) &&
393
393
  !request.callSessionId &&
394
394
  hasLiveQuestionInteraction(request.id)
@@ -581,7 +581,7 @@ export async function routeGuardianReply(
581
581
  if (messageText.length > 0 && pendingRequests.length === 1) {
582
582
  const soleRequest = pendingRequests[0];
583
583
  if (
584
- resolveRequestInstructionMode(soleRequest) === "answer" &&
584
+ resolveGuardianInstructionModeForRequest(soleRequest) === "answer" &&
585
585
  !soleRequest.callSessionId &&
586
586
  soleRequest.sourceConversationId === conversationId &&
587
587
  hasLiveQuestionInteraction(soleRequest.id)
@@ -1005,12 +1005,6 @@ function inferActionFromText(
1005
1005
  return "approve_once";
1006
1006
  }
1007
1007
 
1008
- function resolveRequestInstructionMode(
1009
- request?: Pick<GuardianRequestWire, "kind" | "toolName"> | null,
1010
- ): "approval" | "answer" {
1011
- return resolveGuardianInstructionModeForRequest(request);
1012
- }
1013
-
1014
1008
  // ---------------------------------------------------------------------------
1015
1009
  // Failure reason reply text
1016
1010
  // ---------------------------------------------------------------------------
@@ -1044,7 +1038,7 @@ function failureReplyText(
1044
1038
  return "Something went wrong with this request on our end, so I couldn't apply your decision.";
1045
1039
  case "invalid_action":
1046
1040
  return buildGuardianInvalidActionReply(
1047
- resolveRequestInstructionMode(request),
1041
+ resolveGuardianInstructionModeForRequest(request),
1048
1042
  requestCode ?? undefined,
1049
1043
  );
1050
1044
  default:
@@ -1063,7 +1057,7 @@ function failureReplyText(
1063
1057
  */
1064
1058
  function composeCodeOnlyClarification(request: GuardianRequestWire): string {
1065
1059
  const code = request.requestCode ?? "unknown";
1066
- const mode = resolveRequestInstructionMode(request);
1060
+ const mode = resolveGuardianInstructionModeForRequest(request);
1067
1061
  return buildGuardianCodeOnlyClarification(mode, {
1068
1062
  requestCode: code,
1069
1063
  questionText: request.questionText,
@@ -1087,7 +1081,7 @@ function composeDisambiguationReply(
1087
1081
  const lines: string[] = [];
1088
1082
  const requestsWithMode = pendingRequests.map((request) => ({
1089
1083
  request,
1090
- mode: resolveRequestInstructionMode(request),
1084
+ mode: resolveGuardianInstructionModeForRequest(request),
1091
1085
  }));
1092
1086
 
1093
1087
  if (engineReplyText) {
@@ -2,9 +2,10 @@
2
2
  * Route handler for the POST /v1/btw SSE-streaming side-chain endpoint.
3
3
  *
4
4
  * Runs an ephemeral LLM call that reuses the conversation's provider, tool
5
- * definitions, and message history for prompt-cache efficiency. Uses the
6
- * conversation's system prompt when a conversation-specific override is active;
7
- * otherwise builds a fresh prompt excluding BOOTSTRAP.md so first-run
5
+ * definitions, and message history for prompt-cache efficiency; the
6
+ * empty-state greeting targets no real conversation and sends no tools. Uses
7
+ * the conversation's system prompt when a conversation-specific override is
8
+ * active; otherwise builds a fresh prompt excluding BOOTSTRAP.md so first-run
8
9
  * onboarding instructions don't leak into cosmetic UI calls like identity
9
10
  * intro generation. The response is streamed as SSE events (`btw_text_delta`,
10
11
  * `btw_complete`, `btw_error`).
@@ -145,7 +146,11 @@ async function handleBtw({
145
146
  const result = await runBtwSidechain({
146
147
  content: effectiveContent,
147
148
  conversation,
148
- tools: getAllToolDefinitions(),
149
+ // The side-chain forces `tool_choice: none`, so tool definitions
150
+ // only earn their tokens as a shared cache prefix with a real
151
+ // conversation. The greeting runs against an ephemeral one with its
152
+ // own system prompt, so it shares nothing and sends no tools.
153
+ tools: isGreeting ? [] : getAllToolDefinitions(),
149
154
  signal: abortSignal,
150
155
  ...(isGreeting ? { callSite: "emptyStateGreeting" as const } : {}),
151
156
  onEvent: (event) => {
@@ -228,7 +228,7 @@ function getIdentity() {
228
228
 
229
229
  const version = APP_VERSION;
230
230
 
231
- const createdAt = resolveIdentityCreatedAt(identityPath);
231
+ const createdAt = resolveHatchedAtReadOnly(identityPath);
232
232
 
233
233
  return {
234
234
  name: fields.name ?? "",
@@ -241,10 +241,6 @@ function getIdentity() {
241
241
  };
242
242
  }
243
243
 
244
- function resolveIdentityCreatedAt(identityPath: string): string | undefined {
245
- return resolveHatchedAtReadOnly(identityPath);
246
- }
247
-
248
244
  // ---------------------------------------------------------------------------
249
245
  // Zod schemas for profiler health metadata
250
246
  // ---------------------------------------------------------------------------
@@ -0,0 +1,81 @@
1
+ import { dirname, join, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { describe, expect, test } from "bun:test";
4
+
5
+ import { parseToolManifestFile } from "../../skills/tool-manifest.js";
6
+ import { explicitTools } from "../tool-manifest.js";
7
+
8
+ /**
9
+ * Tool input schemas must be plain objects at the root.
10
+ *
11
+ * Anthropic's Messages API rejects a tool whose `input_schema` carries
12
+ * `oneOf`, `anyOf`, or `allOf` at the top level ("input_schema does not
13
+ * support oneOf, allOf, or anyOf at the top level"). The rejection is a 400
14
+ * for the whole request, so one offending definition takes down every call
15
+ * that advertises it. Other hosts accept the same schema, which lets the
16
+ * mistake hide until a request routes to Anthropic directly. Either/or rules
17
+ * between fields belong in the tool description and the tool's own input
18
+ * validation instead.
19
+ *
20
+ * Covers the core manifest and every bundled skill's `TOOLS.json`.
21
+ * Combinators nested under `properties` are accepted by Anthropic and stay
22
+ * out of scope.
23
+ */
24
+
25
+ const ROOT_COMBINATORS = ["oneOf", "anyOf", "allOf"] as const;
26
+
27
+ const BUNDLED_SKILLS_DIR = resolve(
28
+ dirname(fileURLToPath(import.meta.url)),
29
+ "../../config/bundled-skills",
30
+ );
31
+
32
+ interface SchemaCase {
33
+ /** Tool name, prefixed with the skill directory for bundled skill tools. */
34
+ label: string;
35
+ schema: Record<string, unknown>;
36
+ }
37
+
38
+ function bundledSkillSchemas(): SchemaCase[] {
39
+ const cases: SchemaCase[] = [];
40
+ for (const relative of new Bun.Glob("*/TOOLS.json").scanSync({
41
+ cwd: BUNDLED_SKILLS_DIR,
42
+ })) {
43
+ const manifest = parseToolManifestFile(join(BUNDLED_SKILLS_DIR, relative));
44
+ for (const tool of manifest.tools) {
45
+ cases.push({
46
+ label: `${dirname(relative)}/${tool.name}`,
47
+ schema: tool.input_schema,
48
+ });
49
+ }
50
+ }
51
+ return cases;
52
+ }
53
+
54
+ function coreSchemas(): SchemaCase[] {
55
+ return explicitTools.map((tool) => {
56
+ if (!tool.name) {
57
+ throw new Error("core manifest entries carry explicit names");
58
+ }
59
+ return {
60
+ label: tool.name,
61
+ schema: tool.input_schema as Record<string, unknown>,
62
+ };
63
+ });
64
+ }
65
+
66
+ const CASES: SchemaCase[] = [...coreSchemas(), ...bundledSkillSchemas()];
67
+
68
+ describe("tool input schema root", () => {
69
+ test("covers the core manifest and the bundled skills", () => {
70
+ expect(CASES.length).toBeGreaterThan(explicitTools.length);
71
+ });
72
+
73
+ for (const { label, schema } of CASES) {
74
+ test(`${label} keeps combinators out of the schema root`, () => {
75
+ for (const keyword of ROOT_COMBINATORS) {
76
+ expect(schema).not.toHaveProperty(keyword);
77
+ }
78
+ expect(schema.type).toBe("object");
79
+ });
80
+ }
81
+ });
@@ -124,6 +124,8 @@ const DESCRIPTION = [
124
124
  "For logins, use saved credentials first. Securely collect missing credentials",
125
125
  "with assistant credentials prompt, then fill the login form yourself.",
126
126
  "",
127
+ "Every call passes exactly one of `questions` or `desktopHelp`.",
128
+ "",
127
129
  "Use this tool whenever a request is ambiguous and can be resolved",
128
130
  "by 2–4 plausible interpretations or discrete choices. Prefer it over",
129
131
  "plain-text clarification — structured options are faster to answer and",
@@ -255,10 +257,10 @@ export const askQuestionTool = {
255
257
  category: "interaction",
256
258
  executionTarget: "sandbox",
257
259
  defaultRiskLevel: RiskLevel.Low,
258
- input_schema: {
259
- ...toToolInputSchema(askQuestionInputSchema),
260
- oneOf: [{ required: ["questions"] }, { required: ["desktopHelp"] }],
261
- },
260
+ // Anthropic rejects `oneOf` / `anyOf` / `allOf` at the root of a tool
261
+ // schema, so the either/or rule between `questions` and `desktopHelp` lives
262
+ // in the description and the Zod refine, not in the wire schema.
263
+ input_schema: toToolInputSchema(askQuestionInputSchema),
262
264
 
263
265
  async execute(
264
266
  input: Record<string, unknown>,
@@ -23,10 +23,6 @@ import {
23
23
  } from "../shared/zod-tool-schema.js";
24
24
  import type { ToolContext, ToolExecutionResult } from "../types.js";
25
25
 
26
- function isPrivilegedDocumentActor(context: ToolContext): boolean {
27
- return canActOnPrivilegedDocuments(context);
28
- }
29
-
30
26
  export function documentNotFound(surfaceId: string): ToolExecutionResult {
31
27
  return {
32
28
  content: JSON.stringify({
@@ -43,7 +39,7 @@ export function canAccessDocument(
43
39
  context: ToolContext,
44
40
  ): boolean {
45
41
  return (
46
- isPrivilegedDocumentActor(context) ||
42
+ canActOnPrivilegedDocuments(context) ||
47
43
  isDocumentAssociatedWithConversation(surfaceId, context.conversationId)
48
44
  );
49
45
  }
@@ -522,7 +518,7 @@ export function executeDocumentList(
522
518
  const docs = query
523
519
  ? searchDocumentsByTitle(
524
520
  query,
525
- isPrivilegedDocumentActor(context)
521
+ canActOnPrivilegedDocuments(context)
526
522
  ? {}
527
523
  : { conversationId: context.conversationId },
528
524
  )