@vellumai/assistant 0.4.29 → 0.4.30

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 (174) hide show
  1. package/ARCHITECTURE.md +39 -37
  2. package/README.md +5 -6
  3. package/docs/runbook-trusted-contacts.md +79 -43
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
  6. package/scripts/test.sh +1 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
  8. package/src/__tests__/actor-token-service.test.ts +4 -3
  9. package/src/__tests__/app-executors.test.ts +7 -17
  10. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
  11. package/src/__tests__/browser-skill-endstate.test.ts +10 -1
  12. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
  13. package/src/__tests__/channel-approval-routes.test.ts +44 -44
  14. package/src/__tests__/channel-approval.test.ts +8 -0
  15. package/src/__tests__/channel-approvals.test.ts +39 -1
  16. package/src/__tests__/channel-guardian.test.ts +15 -5
  17. package/src/__tests__/channel-reply-delivery.test.ts +31 -0
  18. package/src/__tests__/commit-message-enrichment-service.test.ts +4 -0
  19. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
  20. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  21. package/src/__tests__/gemini-image-service.test.ts +2 -2
  22. package/src/__tests__/guardian-grant-minting.test.ts +6 -6
  23. package/src/__tests__/guardian-routing-invariants.test.ts +34 -11
  24. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
  25. package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
  26. package/src/__tests__/integrations-cli.test.ts +3 -27
  27. package/src/__tests__/intent-routing.test.ts +3 -0
  28. package/src/__tests__/invite-redemption-service.test.ts +1 -1
  29. package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
  30. package/src/__tests__/ipc-snapshot.test.ts +4 -31
  31. package/src/__tests__/nl-approval-parser.test.ts +305 -0
  32. package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
  33. package/src/__tests__/provider-error-scenarios.test.ts +68 -0
  34. package/src/__tests__/relay-server.test.ts +1 -1
  35. package/src/__tests__/retry-after-extraction.test.ts +111 -0
  36. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
  37. package/src/__tests__/session-media-retry.test.ts +147 -0
  38. package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
  39. package/src/__tests__/skill-feature-flags.test.ts +18 -12
  40. package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
  41. package/src/__tests__/slack-block-formatting.test.ts +100 -0
  42. package/src/__tests__/slack-inbound-verification.test.ts +346 -0
  43. package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
  44. package/src/__tests__/slack-skill.test.ts +3 -2
  45. package/src/__tests__/starter-task-flow.test.ts +0 -1
  46. package/src/__tests__/trusted-contact-verification.test.ts +3 -1
  47. package/src/__tests__/voice-invite-redemption.test.ts +1 -1
  48. package/src/amazon/client.ts +7 -24
  49. package/src/calls/relay-server.ts +39 -11
  50. package/src/channels/config.ts +1 -1
  51. package/src/cli/integrations.ts +10 -66
  52. package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
  53. package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
  54. package/src/config/bundled-skills/browser/TOOLS.json +59 -2
  55. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
  56. package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
  57. package/src/config/bundled-skills/contacts/SKILL.md +42 -35
  58. package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
  59. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +38 -58
  60. package/src/config/bundled-skills/contacts/tools/contact-search.ts +11 -31
  61. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +19 -37
  62. package/src/config/bundled-skills/document/TOOLS.json +8 -0
  63. package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
  64. package/src/config/bundled-skills/followups/TOOLS.json +12 -0
  65. package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
  66. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
  67. package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
  68. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
  69. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
  70. package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
  71. package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
  72. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
  73. package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
  74. package/src/config/bundled-skills/notifications/SKILL.md +3 -2
  75. package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
  76. package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
  77. package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
  78. package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
  79. package/src/config/bundled-skills/schedule/SKILL.md +33 -15
  80. package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
  81. package/src/config/bundled-skills/slack/SKILL.md +30 -1
  82. package/src/config/bundled-skills/slack/TOOLS.json +89 -2
  83. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
  84. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
  85. package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
  86. package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
  87. package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
  88. package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
  89. package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
  90. package/src/config/bundled-skills/weather/TOOLS.json +4 -0
  91. package/src/config/bundled-tool-registry.ts +2 -0
  92. package/src/config/channel-permission-profiles.ts +155 -0
  93. package/src/config/env.ts +4 -1
  94. package/src/contacts/contact-store.ts +195 -4
  95. package/src/contacts/types.ts +26 -0
  96. package/src/daemon/assistant-attachments.ts +23 -3
  97. package/src/daemon/guardian-verification-intent.ts +7 -4
  98. package/src/daemon/handlers/apps.ts +1 -2
  99. package/src/daemon/handlers/config-inbox.ts +16 -134
  100. package/src/daemon/handlers/guardian-actions.ts +20 -87
  101. package/src/daemon/handlers/sessions.ts +0 -1
  102. package/src/daemon/ipc-contract/apps.ts +0 -1
  103. package/src/daemon/ipc-contract/inbox.ts +7 -66
  104. package/src/daemon/ipc-contract/sessions.ts +1 -0
  105. package/src/daemon/ipc-contract/surfaces.ts +0 -1
  106. package/src/daemon/ipc-contract-inventory.json +2 -4
  107. package/src/daemon/lifecycle.ts +14 -2
  108. package/src/daemon/session-agent-loop-handlers.ts +9 -0
  109. package/src/daemon/session-agent-loop.ts +1 -0
  110. package/src/daemon/session-attachments.ts +5 -1
  111. package/src/daemon/session-error.ts +18 -0
  112. package/src/daemon/session-lifecycle.ts +4 -5
  113. package/src/daemon/session-media-retry.ts +15 -1
  114. package/src/daemon/session-surfaces.ts +0 -1
  115. package/src/daemon/session-tool-setup.ts +7 -4
  116. package/src/events/domain-events.ts +2 -1
  117. package/src/home-base/prebuilt/seed.ts +0 -1
  118. package/src/influencer/client.ts +7 -24
  119. package/src/media/gemini-image-service.ts +48 -3
  120. package/src/memory/app-store.ts +0 -4
  121. package/src/memory/conversation-attention-store.ts +3 -1
  122. package/src/memory/db-init.ts +4 -0
  123. package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
  124. package/src/memory/migrations/index.ts +1 -0
  125. package/src/memory/schema.ts +12 -0
  126. package/src/memory/slack-thread-store.ts +187 -0
  127. package/src/messaging/providers/slack/client.ts +84 -26
  128. package/src/messaging/providers/slack/types.ts +4 -0
  129. package/src/notifications/adapters/slack.ts +90 -0
  130. package/src/notifications/destination-resolver.ts +42 -1
  131. package/src/notifications/emit-signal.ts +17 -1
  132. package/src/oauth/provider-profiles.ts +22 -0
  133. package/src/providers/anthropic/client.ts +3 -0
  134. package/src/providers/openai/client.ts +3 -0
  135. package/src/providers/retry.ts +9 -1
  136. package/src/runtime/actor-trust-resolver.ts +8 -0
  137. package/src/runtime/auth/require-bound-guardian.ts +44 -0
  138. package/src/runtime/auth/route-policy.ts +4 -8
  139. package/src/runtime/channel-approval-types.ts +18 -0
  140. package/src/runtime/channel-approvals.ts +8 -0
  141. package/src/runtime/channel-invite-transport.ts +1 -1
  142. package/src/runtime/channel-reply-delivery.ts +62 -3
  143. package/src/runtime/gateway-client.ts +36 -2
  144. package/src/runtime/gateway-internal-client.ts +86 -0
  145. package/src/runtime/guardian-action-service.ts +127 -0
  146. package/src/runtime/guardian-verification-templates.ts +16 -1
  147. package/src/runtime/http-server.ts +20 -49
  148. package/src/runtime/invite-redemption-service.ts +1 -1
  149. package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
  150. package/src/runtime/nl-approval-parser.ts +138 -0
  151. package/src/runtime/routes/approval-routes.ts +1 -40
  152. package/src/runtime/routes/channel-route-shared.ts +35 -1
  153. package/src/runtime/routes/contact-routes.ts +196 -28
  154. package/src/runtime/routes/guardian-action-routes.ts +19 -111
  155. package/src/runtime/routes/guardian-approval-interception.ts +76 -0
  156. package/src/runtime/routes/inbound-message-handler.ts +40 -12
  157. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +222 -0
  158. package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
  159. package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
  160. package/src/runtime/slack-block-formatting.ts +176 -0
  161. package/src/schedule/scheduler.ts +11 -2
  162. package/src/tools/apps/executors.ts +16 -15
  163. package/src/tools/calls/call-end.ts +1 -1
  164. package/src/tools/computer-use/definitions.ts +16 -0
  165. package/src/tools/credentials/vault.ts +86 -2
  166. package/src/tools/network/script-proxy/session-manager.ts +28 -3
  167. package/src/tools/permission-checker.ts +18 -0
  168. package/src/tools/terminal/shell.ts +15 -5
  169. package/src/tools/tool-approval-handler.ts +48 -4
  170. package/src/tools/types.ts +38 -1
  171. package/src/util/errors.ts +5 -1
  172. package/src/util/retry.ts +21 -0
  173. package/src/watcher/providers/slack.ts +33 -3
  174. /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
@@ -1,8 +1,10 @@
1
1
  import { consumeGrantForInvocation } from "../approvals/approval-primitive.js";
2
+ import { isToolAllowedInChannel } from "../config/channel-permission-profiles.js";
2
3
  import {
3
4
  getCanonicalGuardianRequest,
4
5
  updateCanonicalGuardianRequest,
5
6
  } from "../memory/canonical-guardian-store.js";
7
+ import { isUntrustedTrustClass } from "../runtime/actor-trust-resolver.js";
6
8
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
7
9
  import { createOrReuseToolGrantRequest } from "../runtime/tool-grant-request-helper.js";
8
10
  import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
@@ -129,10 +131,6 @@ export async function waitForInlineGrant(
129
131
  return { outcome: "timeout", requestId: escalationRequestId };
130
132
  }
131
133
 
132
- function isUntrustedTrustClass(role: ToolContext["trustClass"]): boolean {
133
- return role === "trusted_contact" || role === "unknown";
134
- }
135
-
136
134
  function requiresGuardianApprovalForActor(
137
135
  toolName: string,
138
136
  input: Record<string, unknown>,
@@ -365,6 +363,52 @@ export class ToolApprovalHandler {
365
363
  return { allowed: false, result: { content: msg, isError: true } };
366
364
  }
367
365
 
366
+ // Enforce channel-scoped permission profiles (deterministic gate).
367
+ // When the session originates from a Slack channel with a configured
368
+ // permission profile, blocked tools and category restrictions are
369
+ // enforced here rather than relying on model compliance with hints.
370
+ if (
371
+ context.executionChannel === "slack" &&
372
+ context.channelPermissionChannelId
373
+ ) {
374
+ if (
375
+ !isToolAllowedInChannel(
376
+ context.channelPermissionChannelId,
377
+ name,
378
+ tool.category,
379
+ )
380
+ ) {
381
+ const msg = `Tool "${name}" is not allowed in this channel per channel permission policy.`;
382
+ log.warn(
383
+ {
384
+ toolName: name,
385
+ channelId: context.channelPermissionChannelId,
386
+ category: tool.category,
387
+ sessionId: context.sessionId,
388
+ conversationId: context.conversationId,
389
+ reason: "channel_permission_policy",
390
+ },
391
+ "Channel permission policy blocked tool invocation",
392
+ );
393
+ const durationMs = Date.now() - startTime;
394
+ emitLifecycleEvent({
395
+ type: "permission_denied",
396
+ toolName: name,
397
+ executionTarget,
398
+ input,
399
+ workingDir: context.workingDir,
400
+ sessionId: context.sessionId,
401
+ conversationId: context.conversationId,
402
+ requestId: context.requestId,
403
+ riskLevel,
404
+ decision: "deny",
405
+ reason: msg,
406
+ durationMs,
407
+ });
408
+ return { allowed: false, result: { content: msg, isError: true } };
409
+ }
410
+ }
411
+
368
412
  // All policy gates passed. Now consume the scoped grant if one is
369
413
  // required. Deferring consumption to this point ensures a downstream
370
414
  // rejection (allowedToolNames, task-run preflight, registry lookup)
@@ -1,4 +1,3 @@
1
- import type { ProxyApprovalCallback } from "../outbound-proxy/index.js";
2
1
  import type { SecretPromptResult } from "../permissions/secret-prompter.js";
3
2
  import type {
4
3
  AllowlistOption,
@@ -166,6 +165,8 @@ export interface ToolContext {
166
165
  requesterExternalUserId?: string;
167
166
  /** Chat ID of the requester (non-guardian actor). Used for tool grant request escalation notifications. */
168
167
  requesterChatId?: string;
168
+ /** Slack channel ID for channel-scoped permission enforcement. When set, tools are checked against the channel's permission profile. */
169
+ channelPermissionChannelId?: string;
169
170
  }
170
171
 
171
172
  export interface DiffInfo {
@@ -200,6 +201,42 @@ export interface ToolExecutionResult {
200
201
  yieldToUser?: boolean;
201
202
  }
202
203
 
204
+ // ---------------------------------------------------------------------------
205
+ // Proxy approval types — local definitions for the outbound-proxy contract.
206
+ // The proxy service owns the canonical shapes; these are the assistant's
207
+ // minimal view of the approval callback interface.
208
+ // ---------------------------------------------------------------------------
209
+
210
+ /** Approval request from the outbound proxy when a policy decision requires user confirmation. */
211
+ export interface ProxyApprovalRequest {
212
+ decision: {
213
+ kind: "ask_missing_credential" | "ask_unauthenticated";
214
+ target: {
215
+ hostname: string;
216
+ port: number | null;
217
+ path: string;
218
+ scheme: "http" | "https";
219
+ };
220
+ /** Present when kind is "ask_missing_credential". */
221
+ matchingPatterns?: string[];
222
+ };
223
+ sessionId: string;
224
+ }
225
+
226
+ /** Callback for proxy policy decisions requiring user confirmation. Returns true if approved. */
227
+ export type ProxyApprovalCallback = (
228
+ request: ProxyApprovalRequest,
229
+ ) => Promise<boolean>;
230
+
231
+ /** Env vars a proxy session injects into child processes. */
232
+ export interface ProxyEnvVars {
233
+ HTTP_PROXY: string;
234
+ HTTPS_PROXY: string;
235
+ NO_PROXY: string;
236
+ NODE_EXTRA_CA_CERTS?: string;
237
+ SSL_CERT_FILE?: string;
238
+ }
239
+
203
240
  export interface Tool {
204
241
  name: string;
205
242
  description: string;
@@ -109,14 +109,18 @@ export class AssistantError extends VellumError {
109
109
  }
110
110
 
111
111
  export class ProviderError extends AssistantError {
112
+ /** Delay (in ms) suggested by the server's Retry-After header, if present. */
113
+ public readonly retryAfterMs?: number;
114
+
112
115
  constructor(
113
116
  message: string,
114
117
  public readonly provider: string,
115
118
  public readonly statusCode?: number,
116
- options?: { cause?: unknown },
119
+ options?: { cause?: unknown; retryAfterMs?: number },
117
120
  ) {
118
121
  super(message, ErrorCode.PROVIDER_ERROR, options);
119
122
  this.name = "ProviderError";
123
+ this.retryAfterMs = options?.retryAfterMs;
120
124
  }
121
125
  }
122
126
 
package/src/util/retry.ts CHANGED
@@ -125,4 +125,25 @@ export function abortableSleep(
125
125
  });
126
126
  }
127
127
 
128
+ /**
129
+ * Extract retry-after milliseconds from an SDK error's headers object.
130
+ * Handles both Map-like (OpenAI: Headers with .get()) and plain-object
131
+ * (Anthropic: Record<string, string>) header shapes.
132
+ */
133
+ export function extractRetryAfterMs(headers: unknown): number | undefined {
134
+ if (!headers) return undefined;
135
+
136
+ let raw: string | null | undefined;
137
+ if (typeof (headers as { get?: unknown }).get === "function") {
138
+ raw = (headers as { get(k: string): string | null }).get("retry-after");
139
+ } else if (typeof headers === "object") {
140
+ raw = (headers as Record<string, string>)["retry-after"];
141
+ }
142
+
143
+ if (typeof raw === "string") {
144
+ return parseRetryAfterMs(raw);
145
+ }
146
+ return undefined;
147
+ }
148
+
128
149
  export { DEFAULT_BASE_DELAY_MS, DEFAULT_MAX_RETRIES };
@@ -25,6 +25,20 @@ const log = getLogger("watcher:slack");
25
25
  * When `channels` is specified, those channels are monitored for ALL messages
26
26
  * (not just mentions). When omitted or empty, the legacy behavior applies:
27
27
  * @mentions in the top 20 member channels.
28
+ *
29
+ * ## Socket Mode Redundancy
30
+ *
31
+ * When the gateway is running with Socket Mode enabled, it already receives
32
+ * real-time events for @mentions, DMs, and active thread replies. The watcher's
33
+ * polling is therefore redundant for those event types. Set
34
+ * `socketModeActive: true` to skip polling for DMs and @mentions that Socket
35
+ * Mode already covers. The watcher will still poll for configured `channels`
36
+ * (all-message monitoring) since Socket Mode only forwards messages where the
37
+ * bot is mentioned or in active threads.
38
+ *
39
+ * @deprecated The polling-based watcher for DMs and @mentions is superseded by
40
+ * Socket Mode in the gateway. Once Socket Mode is confirmed stable, the DM and
41
+ * mention-polling code paths can be removed entirely.
28
42
  */
29
43
  interface SlackWatcherConfig {
30
44
  /** Channel IDs to watch for all messages (e.g. ["C01234ABCDE"]).
@@ -32,6 +46,12 @@ interface SlackWatcherConfig {
32
46
  channels?: string[];
33
47
  /** When true, also monitor DMs and group DMs (default: true). */
34
48
  includeDMs?: boolean;
49
+ /**
50
+ * When true, skip polling for DMs and @mentions since the gateway's Socket
51
+ * Mode connection already delivers those events in real-time. The watcher
52
+ * will still poll explicitly configured `channels` for all-message monitoring.
53
+ */
54
+ socketModeActive?: boolean;
35
55
  }
36
56
 
37
57
  function messageToItem(
@@ -142,6 +162,13 @@ export const slackProvider: WatcherProvider = {
142
162
  ? slackConfig.includeDMs
143
163
  : true;
144
164
 
165
+ const socketModeActive = slackConfig.socketModeActive === true;
166
+ if (socketModeActive) {
167
+ log.info(
168
+ "Socket Mode active — skipping DM and @mention polling (handled by gateway)",
169
+ );
170
+ }
171
+
145
172
  const authResp = await slack.authTest(token);
146
173
  const userId = authResp.user_id;
147
174
 
@@ -149,7 +176,8 @@ export const slackProvider: WatcherProvider = {
149
176
  let latestTs = watermark;
150
177
 
151
178
  // ── DM / Group DM polling ──────────────────────────────────────
152
- if (includeDMs) {
179
+ // Skip when Socket Mode is active — the gateway already delivers DM events in real-time.
180
+ if (includeDMs && !socketModeActive) {
153
181
  const convResp = await slack.listConversations(
154
182
  token,
155
183
  "im,mpim",
@@ -204,8 +232,10 @@ export const slackProvider: WatcherProvider = {
204
232
  );
205
233
  }
206
234
  }
207
- } else {
208
- // Legacy behavior: check top 20 member channels for @mentions only
235
+ } else if (!socketModeActive) {
236
+ // Legacy behavior: check top 20 member channels for @mentions only.
237
+ // Skipped when Socket Mode is active — the gateway already delivers
238
+ // app_mention events in real-time.
209
239
  const memberConvResp = await slack.listConversations(
210
240
  token,
211
241
  "public_channel,private_channel",