@vellumai/assistant 0.5.14 → 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.
- package/ARCHITECTURE.md +2 -2
- package/docs/architecture/integrations.md +15 -14
- package/knip.json +3 -1
- package/openapi.yaml +11 -43
- package/package.json +1 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -375
- package/src/__tests__/ces-rpc-credential-backend.test.ts +4 -1
- package/src/__tests__/checker.test.ts +59 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +98 -10
- package/src/__tests__/cli-memory.test.ts +372 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
- package/src/__tests__/config-schema.test.ts +0 -2
- package/src/__tests__/config-watcher-feature-flags.test.ts +211 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +2 -6
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +4 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
- package/src/__tests__/guardian-routing-invariants.test.ts +151 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -3
- package/src/__tests__/intent-routing.test.ts +6 -18
- package/src/__tests__/log-export-workspace.test.ts +2 -28
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
- package/src/__tests__/managed-store.test.ts +2 -10
- package/src/__tests__/messaging-send-tool.test.ts +6 -6
- package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
- package/src/__tests__/migration-export-http.test.ts +3 -34
- package/src/__tests__/migration-import-commit-http.test.ts +1 -29
- package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
- package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
- package/src/__tests__/oauth-apps-routes.test.ts +120 -10
- package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
- package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
- package/src/__tests__/oauth-providers-routes.test.ts +5 -2
- package/src/__tests__/oauth-store.test.ts +0 -5
- package/src/__tests__/outlook-messaging-provider.test.ts +576 -0
- package/src/__tests__/path-policy.test.ts +2 -17
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/platform-callback-registration.test.ts +3 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -2
- package/src/__tests__/qdrant-manager.test.ts +68 -21
- package/src/__tests__/require-fresh-approval.test.ts +0 -1
- package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
- package/src/__tests__/secret-allowlist.test.ts +20 -35
- package/src/__tests__/shell-credential-ref.test.ts +0 -5
- package/src/__tests__/skill-load-feature-flag.test.ts +2 -43
- package/src/__tests__/skill-load-inline-command.test.ts +3 -65
- package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
- package/src/__tests__/skill-load-tool.test.ts +3 -67
- package/src/__tests__/skill-memory.test.ts +362 -119
- package/src/__tests__/skills.test.ts +22 -49
- package/src/__tests__/slack-channel-config.test.ts +2 -21
- package/src/__tests__/starter-bundle.test.ts +2 -8
- package/src/__tests__/stt-hints.test.ts +7 -2
- package/src/__tests__/system-prompt.test.ts +25 -45
- package/src/__tests__/task-compiler.test.ts +0 -21
- package/src/__tests__/task-management-tools.test.ts +0 -21
- package/src/__tests__/task-memory-cleanup.test.ts +0 -21
- package/src/__tests__/task-runner.test.ts +0 -21
- package/src/__tests__/task-scheduler.test.ts +0 -21
- package/src/__tests__/terminal-tools.test.ts +1 -17
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
- package/src/__tests__/tool-approval-handler.test.ts +1 -20
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -20
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
- package/src/__tests__/trust-store.test.ts +9 -41
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -21
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -22
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -22
- package/src/__tests__/trusted-contact-verification.test.ts +0 -22
- package/src/__tests__/turn-boundary-resolution.test.ts +0 -28
- package/src/__tests__/twilio-provider.test.ts +0 -16
- package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
- package/src/__tests__/twilio-routes.test.ts +0 -24
- package/src/__tests__/update-bulletin.test.ts +17 -89
- package/src/__tests__/usage-cache-backfill-migration.test.ts +0 -20
- package/src/__tests__/usage-routes.test.ts +0 -21
- package/src/__tests__/user-reference.test.ts +1 -5
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
- package/src/__tests__/voice-invite-redemption.test.ts +0 -21
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -24
- package/src/__tests__/voice-session-bridge.test.ts +0 -21
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -23
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -23
- package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
- package/src/acp/client-handler.ts +1 -2
- package/src/cli/__tests__/notifications.test.ts +0 -22
- package/src/cli/cli-memory.ts +176 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +15 -0
- package/src/cli/commands/oauth/providers.ts +49 -42
- package/src/cli/commands/platform/__tests__/connect.test.ts +2 -48
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +2 -48
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -50
- package/src/config/bundled-skills/computer-use/TOOLS.json +7 -7
- package/src/config/bundled-skills/messaging/SKILL.md +17 -2
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +8 -0
- package/src/context/window-manager.ts +28 -9
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/daemon/config-watcher.ts +51 -0
- package/src/daemon/conversation-agent-loop.ts +3 -2
- package/src/daemon/conversation-process.ts +1 -0
- package/src/daemon/conversation-usage.ts +1 -0
- package/src/daemon/handlers/skills.ts +9 -1
- package/src/daemon/lifecycle.ts +13 -4
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/providers-setup.ts +2 -0
- package/src/daemon/server.ts +26 -22
- package/src/events/domain-events.ts +1 -2
- package/src/memory/db-init.ts +9 -0
- package/src/memory/job-handlers/batch-extraction.ts +16 -4
- package/src/memory/job-handlers/embedding.test.ts +3 -27
- package/src/memory/job-handlers/journal-carry-forward.test.ts +1 -29
- package/src/memory/llm-usage-store.ts +35 -2
- package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
- package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/qdrant-manager.ts +26 -5
- package/src/memory/query-expansion.ts +1 -1
- package/src/memory/retriever.test.ts +22 -20
- package/src/memory/retriever.ts +10 -2
- package/src/memory/schema/oauth.ts +1 -1
- package/src/memory/search/mmr.ts +8 -5
- package/src/memory/slack-thread-store.ts +17 -0
- package/src/messaging/providers/outlook/adapter.ts +193 -0
- package/src/messaging/providers/outlook/client.ts +311 -0
- package/src/messaging/providers/outlook/types.ts +83 -0
- package/src/notifications/adapters/slack.ts +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
- package/src/oauth/connect-orchestrator.ts +10 -3
- package/src/oauth/oauth-store.ts +10 -11
- package/src/oauth/provider-serializer.ts +3 -0
- package/src/oauth/provider-visibility.ts +16 -0
- package/src/oauth/seed-providers.ts +49 -17
- package/src/permissions/checker.ts +39 -7
- package/src/permissions/types.ts +2 -4
- package/src/prompts/journal-context.ts +9 -11
- package/src/prompts/system-prompt.ts +3 -64
- package/src/prompts/templates/UPDATES.md +6 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +1 -27
- package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
- package/src/runtime/auth/route-policy.ts +0 -4
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/routes/conversation-query-routes.ts +2 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +43 -2
- package/src/runtime/routes/memory-item-routes.test.ts +0 -17
- package/src/runtime/routes/memory-item-routes.ts +103 -12
- package/src/runtime/routes/oauth-apps.ts +18 -1
- package/src/runtime/routes/oauth-providers.ts +13 -1
- package/src/runtime/routes/settings-routes.ts +1 -0
- package/src/runtime/routes/usage-routes.ts +19 -2
- package/src/runtime/routes/work-items-routes.test.ts +0 -21
- package/src/runtime/routes/workspace-routes.test.ts +3 -27
- package/src/security/secret-allowlist.ts +4 -4
- package/src/skills/skill-memory.ts +62 -23
- package/src/tools/memory/handlers.test.ts +1 -29
- package/src/tools/permission-checker.ts +0 -18
- package/src/tools/skills/skill-script-runner.ts +1 -1
- package/src/util/device-id.ts +3 -65
- package/src/workspace/git-service.ts +27 -6
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { mkdtempSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
2
|
|
|
6
|
-
const testDir = mkdtempSync(join(tmpdir(), "platform-status-test-"));
|
|
7
|
-
|
|
8
3
|
// ---------------------------------------------------------------------------
|
|
9
4
|
// Mock state
|
|
10
5
|
// ---------------------------------------------------------------------------
|
|
@@ -68,51 +63,6 @@ mock.module("../../../../util/logger.js", () => ({
|
|
|
68
63
|
pruneOldLogFiles: () => 0,
|
|
69
64
|
}));
|
|
70
65
|
|
|
71
|
-
mock.module("../../../../util/platform.js", () => ({
|
|
72
|
-
getRootDir: () => testDir,
|
|
73
|
-
getDataDir: () => join(testDir, "data"),
|
|
74
|
-
getWorkspaceSkillsDir: () => join(testDir, "skills"),
|
|
75
|
-
getWorkspaceDir: () => join(testDir, "workspace"),
|
|
76
|
-
getWorkspaceHooksDir: () => join(testDir, "workspace", "hooks"),
|
|
77
|
-
getWorkspaceConfigPath: () => join(testDir, "workspace", "config.json"),
|
|
78
|
-
getHooksDir: () => join(testDir, "hooks"),
|
|
79
|
-
getSignalsDir: () => join(testDir, "signals"),
|
|
80
|
-
getConversationsDir: () => join(testDir, "conversations"),
|
|
81
|
-
getEmbeddingModelsDir: () => join(testDir, "models"),
|
|
82
|
-
getSandboxRootDir: () => join(testDir, "sandbox"),
|
|
83
|
-
getSandboxWorkingDir: () => join(testDir, "sandbox", "work"),
|
|
84
|
-
getInterfacesDir: () => join(testDir, "interfaces"),
|
|
85
|
-
getSoundsDir: () => join(testDir, "sounds"),
|
|
86
|
-
getHistoryPath: () => join(testDir, "history"),
|
|
87
|
-
isMacOS: () => process.platform === "darwin",
|
|
88
|
-
isLinux: () => process.platform === "linux",
|
|
89
|
-
isWindows: () => process.platform === "win32",
|
|
90
|
-
getPlatformName: () => "linux",
|
|
91
|
-
getClipboardCommand: () => null,
|
|
92
|
-
resolveInstanceDataDir: () => undefined,
|
|
93
|
-
normalizeAssistantId: (id: string) => id,
|
|
94
|
-
getTCPPort: () => 0,
|
|
95
|
-
isTCPEnabled: () => false,
|
|
96
|
-
getTCPHost: () => "127.0.0.1",
|
|
97
|
-
isIOSPairingEnabled: () => false,
|
|
98
|
-
getPlatformTokenPath: () => join(testDir, "token"),
|
|
99
|
-
readPlatformToken: () => null,
|
|
100
|
-
getPidPath: () => join(testDir, "test.pid"),
|
|
101
|
-
getDbPath: () => join(testDir, "test.db"),
|
|
102
|
-
getLogPath: () => join(testDir, "test.log"),
|
|
103
|
-
getWorkspaceDirDisplay: () => testDir,
|
|
104
|
-
getWorkspacePromptPath: (file: string) => join(testDir, file),
|
|
105
|
-
getProtectedDir: () => join(testDir, "protected"),
|
|
106
|
-
getDeprecatedDir: () => join(testDir, "workspace", "deprecated"),
|
|
107
|
-
getExternalDir: () => join(testDir, "external"),
|
|
108
|
-
getBinDir: () => join(testDir, "bin"),
|
|
109
|
-
getDotEnvPath: () => join(testDir, ".env"),
|
|
110
|
-
getDaemonStderrLogPath: () => join(testDir, "daemon-stderr.log"),
|
|
111
|
-
getDaemonStartupLockPath: () => join(testDir, "daemon-startup.lock"),
|
|
112
|
-
getEmbedWorkerPidPath: () => join(testDir, "embed-worker.pid"),
|
|
113
|
-
ensureDataDir: () => {},
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
66
|
mock.module("../../../../config/loader.js", () => ({
|
|
117
67
|
API_KEY_PROVIDERS: [] as const,
|
|
118
68
|
getConfig: () => ({
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"name": "computer_use_click",
|
|
24
24
|
"description": "Click an element on screen. Prefer element_id (from the accessibility tree) over x/y coordinates.",
|
|
25
25
|
"category": "computer-use",
|
|
26
|
-
"risk": "
|
|
26
|
+
"risk": "medium",
|
|
27
27
|
"input_schema": {
|
|
28
28
|
"type": "object",
|
|
29
29
|
"properties": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"name": "computer_use_type_text",
|
|
63
63
|
"description": "Type text at the current cursor position. First click a text field (by element_id) to focus it, then call this tool. If a field shows 'FOCUSED', skip the click.",
|
|
64
64
|
"category": "computer-use",
|
|
65
|
-
"risk": "
|
|
65
|
+
"risk": "medium",
|
|
66
66
|
"input_schema": {
|
|
67
67
|
"type": "object",
|
|
68
68
|
"properties": {
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"name": "computer_use_key",
|
|
89
89
|
"description": "Press a key or keyboard shortcut. Supported: enter, tab, escape, backspace, delete, up, down, left, right, space, cmd+a, cmd+c, cmd+v, cmd+z, cmd+tab, cmd+w, shift+tab, option+tab",
|
|
90
90
|
"category": "computer-use",
|
|
91
|
-
"risk": "
|
|
91
|
+
"risk": "medium",
|
|
92
92
|
"input_schema": {
|
|
93
93
|
"type": "object",
|
|
94
94
|
"properties": {
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"name": "computer_use_scroll",
|
|
115
115
|
"description": "Scroll within an element by its [ID], or at raw screen coordinates as fallback.",
|
|
116
116
|
"category": "computer-use",
|
|
117
|
-
"risk": "
|
|
117
|
+
"risk": "medium",
|
|
118
118
|
"input_schema": {
|
|
119
119
|
"type": "object",
|
|
120
120
|
"properties": {
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"name": "computer_use_drag",
|
|
158
158
|
"description": "Drag from one element or position to another. Use for moving files, resizing windows, rearranging items, or adjusting sliders.",
|
|
159
159
|
"category": "computer-use",
|
|
160
|
-
"risk": "
|
|
160
|
+
"risk": "medium",
|
|
161
161
|
"input_schema": {
|
|
162
162
|
"type": "object",
|
|
163
163
|
"properties": {
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
"name": "computer_use_open_app",
|
|
230
230
|
"description": "Open or switch to a macOS application by name. Preferred over cmd+tab for switching apps - more reliable and explicit.",
|
|
231
231
|
"category": "computer-use",
|
|
232
|
-
"risk": "
|
|
232
|
+
"risk": "medium",
|
|
233
233
|
"input_schema": {
|
|
234
234
|
"type": "object",
|
|
235
235
|
"properties": {
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
"name": "computer_use_run_applescript",
|
|
256
256
|
"description": "Run an AppleScript command. Prefer this over click/type when possible - it doesn't move the cursor or interrupt the user. Never use 'do shell script' inside AppleScript (blocked for security).",
|
|
257
257
|
"category": "computer-use",
|
|
258
|
-
"risk": "
|
|
258
|
+
"risk": "medium",
|
|
259
259
|
"input_schema": {
|
|
260
260
|
"type": "object",
|
|
261
261
|
"properties": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: messaging
|
|
3
|
-
description: Read, search, send, and manage messages across Gmail, Telegram, and other platforms
|
|
3
|
+
description: Read, search, send, and manage messages across Gmail, Outlook, Telegram, and other platforms
|
|
4
4
|
compatibility: "Designed for Vellum personal assistants"
|
|
5
5
|
metadata:
|
|
6
6
|
emoji: "\U0001F4AC"
|
|
@@ -11,7 +11,7 @@ metadata:
|
|
|
11
11
|
- "Handles credential flows -- do not improvise setup instructions"
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
You are a unified messaging assistant with access to multiple platforms (Gmail, Telegram, and more). Use the messaging tools to help users read, search, organize, draft, and send messages across all connected platforms.
|
|
14
|
+
You are a unified messaging assistant with access to multiple platforms (Gmail, Outlook, Telegram, and more). Use the messaging tools to help users read, search, organize, draft, and send messages across all connected platforms.
|
|
15
15
|
|
|
16
16
|
**Slack is not handled by this skill.** Slack messaging (send, read, search) is handled by the **slack** skill, which uses the Slack Web API directly via CLI. Do not use messaging tools with `platform: "slack"`.
|
|
17
17
|
|
|
@@ -67,6 +67,11 @@ When the user asks to "connect my email", "set up email", "manage my email", or
|
|
|
67
67
|
- **cancelLabel:** "Not Now"
|
|
68
68
|
- If the user confirms, briefly acknowledge (e.g., "Setting up Gmail now...") and proceed with the setup guide. If they decline, acknowledge and let them know they can set it up later.
|
|
69
69
|
|
|
70
|
+
### Outlook
|
|
71
|
+
|
|
72
|
+
1. **Try connecting directly first.** Run `assistant oauth status outlook`. This will show whether the user has previously connected their Outlook account.
|
|
73
|
+
2. **If no connections are found:** Guide the user to connect their Microsoft account through the OAuth flow.
|
|
74
|
+
|
|
70
75
|
### Slack
|
|
71
76
|
|
|
72
77
|
Slack is **not** handled by this skill. For Slack setup, load the **slack-app-setup** skill directly. For Slack messaging, use the **slack** skill which accesses the Slack Web API via CLI.
|
|
@@ -118,6 +123,16 @@ When a messaging tool fails with a token or authorization error:
|
|
|
118
123
|
- **Send / Reply**: Send a message or reply in a thread (via `thread_id`). High risk - requires user approval.
|
|
119
124
|
- **Mark Read**: Mark conversation as read
|
|
120
125
|
|
|
126
|
+
### Outlook
|
|
127
|
+
|
|
128
|
+
- **Auth Test**: Verify connection and show account info
|
|
129
|
+
- **List Conversations**: Show mail folders (Inbox, Sent, Drafts, etc.) with unread counts
|
|
130
|
+
- **Read Messages**: Read message history from a folder
|
|
131
|
+
- **Search**: Search messages using Microsoft Graph KQL syntax
|
|
132
|
+
- **Send / Reply**: Send a message or reply to a thread (high risk - requires user approval)
|
|
133
|
+
- **Mark Read**: Mark a message as read
|
|
134
|
+
- **Thread Replies**: View all messages in a conversation thread
|
|
135
|
+
|
|
121
136
|
### Telegram
|
|
122
137
|
|
|
123
138
|
Telegram is supported as a messaging provider with limited capabilities compared to Gmail due to Bot API constraints:
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"required": ["image_path"]
|
|
109
109
|
},
|
|
110
110
|
"executor": "tools/avatar-update.ts",
|
|
111
|
-
"execution_target": "
|
|
111
|
+
"execution_target": "host"
|
|
112
112
|
},
|
|
113
113
|
{
|
|
114
114
|
"name": "remove_avatar",
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
127
|
"executor": "tools/avatar-remove.ts",
|
|
128
|
-
"execution_target": "
|
|
128
|
+
"execution_target": "host"
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
"name": "get_avatar",
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
144
|
"executor": "tools/avatar-get.ts",
|
|
145
|
-
"execution_target": "
|
|
145
|
+
"execution_target": "host"
|
|
146
146
|
}
|
|
147
147
|
]
|
|
148
148
|
}
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"description": "Enable the Vellum Cloud hosting option on the Hosting screen",
|
|
26
26
|
"defaultEnabled": false
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
"id": "local-docker-enabled",
|
|
30
|
+
"scope": "macos",
|
|
31
|
+
"key": "local-docker-enabled",
|
|
32
|
+
"label": "Local Docker Mode",
|
|
33
|
+
"description": "When enabled, the Local hosting option uses Docker under the hood for sandboxed execution, hiding the separate Docker card",
|
|
34
|
+
"defaultEnabled": false
|
|
35
|
+
},
|
|
28
36
|
{
|
|
29
37
|
"id": "contacts",
|
|
30
38
|
"scope": "assistant",
|
|
@@ -360,6 +368,14 @@
|
|
|
360
368
|
"label": "Fast Mode",
|
|
361
369
|
"description": "Enable Anthropic fast mode for Opus 4.6, delivering up to 2.5x higher output tokens per second at premium pricing",
|
|
362
370
|
"defaultEnabled": false
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"id": "outlook-oauth-integration",
|
|
374
|
+
"scope": "assistant",
|
|
375
|
+
"key": "outlook-oauth-integration",
|
|
376
|
+
"label": "Outlook / Microsoft Integration",
|
|
377
|
+
"description": "Enable the Outlook / Microsoft OAuth provider for connecting to Microsoft Graph APIs (email, calendar)",
|
|
378
|
+
"defaultEnabled": false
|
|
363
379
|
}
|
|
364
380
|
]
|
|
365
381
|
}
|
package/src/config/loader.ts
CHANGED
|
@@ -124,6 +124,10 @@ const DEPRECATED_FIELDS: Record<string, string> = {
|
|
|
124
124
|
"providerOrder has been removed from the config schema. " +
|
|
125
125
|
"Provider selection is now handled automatically. " +
|
|
126
126
|
"The field will be removed from your config file.",
|
|
127
|
+
"permissions.dangerouslySkipPermissions":
|
|
128
|
+
"permissions.dangerouslySkipPermissions has been removed. " +
|
|
129
|
+
"Permission prompts are now always shown when required. " +
|
|
130
|
+
"The field will be removed from your config file.",
|
|
127
131
|
};
|
|
128
132
|
|
|
129
133
|
/**
|
|
@@ -79,12 +79,6 @@ export const PermissionsConfigSchema = z
|
|
|
79
79
|
.describe(
|
|
80
80
|
"Permission mode — 'strict' requires explicit approval for all operations, 'workspace' allows operations within the workspace",
|
|
81
81
|
),
|
|
82
|
-
dangerouslySkipPermissions: z
|
|
83
|
-
.boolean({
|
|
84
|
-
error: "permissions.dangerouslySkipPermissions must be a boolean",
|
|
85
|
-
})
|
|
86
|
-
.default(false)
|
|
87
|
-
.describe("Auto-accept all permission prompts without asking"),
|
|
88
82
|
})
|
|
89
83
|
.describe("Permission enforcement mode for tool operations");
|
|
90
84
|
|
|
@@ -51,6 +51,11 @@ export const GoogleOAuthServiceSchema = BaseServiceSchema.extend({
|
|
|
51
51
|
});
|
|
52
52
|
export type GoogleOAuthService = z.infer<typeof GoogleOAuthServiceSchema>;
|
|
53
53
|
|
|
54
|
+
export const OutlookOAuthServiceSchema = BaseServiceSchema.extend({
|
|
55
|
+
mode: ServiceModeSchema.default("your-own"),
|
|
56
|
+
});
|
|
57
|
+
export type OutlookOAuthService = z.infer<typeof OutlookOAuthServiceSchema>;
|
|
58
|
+
|
|
54
59
|
export const ServicesSchema = z.object({
|
|
55
60
|
inference: InferenceServiceSchema.default(InferenceServiceSchema.parse({})),
|
|
56
61
|
"image-generation": ImageGenerationServiceSchema.default(
|
|
@@ -62,5 +67,8 @@ export const ServicesSchema = z.object({
|
|
|
62
67
|
"google-oauth": GoogleOAuthServiceSchema.default(
|
|
63
68
|
GoogleOAuthServiceSchema.parse({}),
|
|
64
69
|
),
|
|
70
|
+
"outlook-oauth": OutlookOAuthServiceSchema.default(
|
|
71
|
+
OutlookOAuthServiceSchema.parse({}),
|
|
72
|
+
),
|
|
65
73
|
});
|
|
66
74
|
export type Services = z.infer<typeof ServicesSchema>;
|
|
@@ -581,7 +581,7 @@ export class ContextWindowManager {
|
|
|
581
581
|
const overheadTokens =
|
|
582
582
|
estimateTextTokens(SUMMARY_SYSTEM_PROMPT) +
|
|
583
583
|
estimateTextTokens(currentSummary) +
|
|
584
|
-
// Scaffolding text in
|
|
584
|
+
// Scaffolding text in buildSummaryContentBlocks ("Update the summary...",
|
|
585
585
|
// section headers, etc.) — generous fixed estimate.
|
|
586
586
|
200 +
|
|
587
587
|
this.summaryMaxTokens;
|
|
@@ -598,6 +598,7 @@ export class ContextWindowManager {
|
|
|
598
598
|
for (const block of blocks) {
|
|
599
599
|
totalTokens += estimateBlockTokens(block);
|
|
600
600
|
}
|
|
601
|
+
const originalTotalTokens = totalTokens;
|
|
601
602
|
if (totalTokens <= maxTranscriptTokens) return blocks;
|
|
602
603
|
|
|
603
604
|
// First pass: drop images from the beginning until we fit or run out of
|
|
@@ -618,17 +619,39 @@ export class ContextWindowManager {
|
|
|
618
619
|
if (totalTokens <= maxTranscriptTokens) return result;
|
|
619
620
|
|
|
620
621
|
// Second pass: drop text blocks from the beginning (oldest) until we fit.
|
|
622
|
+
// If a single text block exceeds the remaining budget, truncate it rather
|
|
623
|
+
// than dropping it entirely so the summarizer always has content to work with.
|
|
621
624
|
let dropUntil = 0;
|
|
622
625
|
let droppedTokens = 0;
|
|
623
626
|
for (let i = 0; i < result.length && totalTokens > maxTranscriptTokens; i++) {
|
|
624
|
-
|
|
625
|
-
totalTokens
|
|
627
|
+
const blockTokens = estimateBlockTokens(result[i]);
|
|
628
|
+
const excess = totalTokens - maxTranscriptTokens;
|
|
629
|
+
if (blockTokens > excess && result[i].type === "text") {
|
|
630
|
+
// Truncate this block to shed exactly the excess tokens.
|
|
631
|
+
const keepTokens = blockTokens - excess;
|
|
632
|
+
const text = (result[i] as { type: "text"; text: string }).text;
|
|
633
|
+
// Approximate: 1 token ≈ 4 characters for truncation purposes.
|
|
634
|
+
const keepChars = Math.max(1, Math.floor(keepTokens * 4));
|
|
635
|
+
const truncatedText = text.slice(-keepChars);
|
|
636
|
+
const truncatedBlock: ContentBlock = {
|
|
637
|
+
type: "text",
|
|
638
|
+
text: `[...truncated] ${truncatedText}`,
|
|
639
|
+
};
|
|
640
|
+
const newBlockTokens = estimateBlockTokens(truncatedBlock);
|
|
641
|
+
droppedTokens += blockTokens - newBlockTokens;
|
|
642
|
+
totalTokens -= blockTokens - newBlockTokens;
|
|
643
|
+
result[i] = truncatedBlock;
|
|
644
|
+
dropUntil = i;
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
droppedTokens += blockTokens;
|
|
648
|
+
totalTokens -= blockTokens;
|
|
626
649
|
dropUntil = i + 1;
|
|
627
650
|
}
|
|
628
651
|
|
|
629
652
|
log.info(
|
|
630
653
|
{
|
|
631
|
-
originalTokens:
|
|
654
|
+
originalTokens: originalTotalTokens,
|
|
632
655
|
cappedTokens: maxTranscriptTokens,
|
|
633
656
|
droppedTokens,
|
|
634
657
|
},
|
|
@@ -892,11 +915,7 @@ function serializeMessagesToContentBlocks(messages: Message[]): ContentBlock[] {
|
|
|
892
915
|
textLines.length = 0;
|
|
893
916
|
}
|
|
894
917
|
blocks.push(block);
|
|
895
|
-
} else if (block.type === "
|
|
896
|
-
textLines.push(
|
|
897
|
-
`web_search_tool_result ${block.tool_use_id}: [opaque]`,
|
|
898
|
-
);
|
|
899
|
-
} else if (block.type === "tool_result") {
|
|
918
|
+
} else if (block.type === "tool_result") { // guard:allow-tool-result-only — web_search_tool_result handled by serializeBlock via else branch
|
|
900
919
|
// Extract images from tool_result contentBlocks before serializing.
|
|
901
920
|
const collectedImages: ImageContent[] = [];
|
|
902
921
|
textLines.push(serializeToolResultBlock(block, collectedImages));
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
readdirSync,
|
|
11
11
|
watch,
|
|
12
12
|
} from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
13
14
|
import { join } from "node:path";
|
|
14
15
|
|
|
16
|
+
import { clearFeatureFlagOverridesCache } from "../config/assistant-feature-flags.js";
|
|
15
17
|
import { getConfig, invalidateConfigCache } from "../config/loader.js";
|
|
16
18
|
import { clearEmbeddingBackendCache } from "../memory/embedding-backend.js";
|
|
17
19
|
import { clearCache as clearTrustCache } from "../permissions/trust-store.js";
|
|
@@ -173,6 +175,7 @@ export class ConfigWatcher {
|
|
|
173
175
|
"workspace directory for config/prompt changes",
|
|
174
176
|
);
|
|
175
177
|
|
|
178
|
+
this.startFeatureFlagsWatcher();
|
|
176
179
|
this.startSignalsWatcher();
|
|
177
180
|
this.startSkillsWatchers(onConversationEvict);
|
|
178
181
|
}
|
|
@@ -185,6 +188,54 @@ export class ConfigWatcher {
|
|
|
185
188
|
this.watchers = [];
|
|
186
189
|
}
|
|
187
190
|
|
|
191
|
+
private startFeatureFlagsWatcher(): void {
|
|
192
|
+
const protectedDir = process.env.GATEWAY_SECURITY_DIR
|
|
193
|
+
? process.env.GATEWAY_SECURITY_DIR
|
|
194
|
+
: join(homedir(), ".vellum", "protected");
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
if (!existsSync(protectedDir)) {
|
|
198
|
+
mkdirSync(protectedDir, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// If we can't create it, watching will also fail — handled below.
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const FLAG_FILES = new Set([
|
|
205
|
+
"feature-flags.json",
|
|
206
|
+
"feature-flags-remote.json",
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const watcher = watch(protectedDir, (_eventType, filename) => {
|
|
211
|
+
if (!filename) return;
|
|
212
|
+
const file = String(filename);
|
|
213
|
+
if (!FLAG_FILES.has(file)) return;
|
|
214
|
+
this.debounceTimers.schedule(
|
|
215
|
+
"file:feature-flags",
|
|
216
|
+
() => {
|
|
217
|
+
log.info(
|
|
218
|
+
{ file },
|
|
219
|
+
"Feature flags file changed, invalidating cache",
|
|
220
|
+
);
|
|
221
|
+
clearFeatureFlagOverridesCache();
|
|
222
|
+
},
|
|
223
|
+
500,
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
this.watchers.push(watcher);
|
|
227
|
+
log.info(
|
|
228
|
+
{ dir: protectedDir },
|
|
229
|
+
"Watching protected directory for feature flag changes",
|
|
230
|
+
);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
log.warn(
|
|
233
|
+
{ err, dir: protectedDir },
|
|
234
|
+
"Failed to watch protected directory for feature flags. Flag changes will require a restart.",
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
188
239
|
private startSignalsWatcher(): void {
|
|
189
240
|
const signalsDir = getSignalsDir();
|
|
190
241
|
try {
|
|
@@ -1587,14 +1587,15 @@ export async function runAgentLoopImpl(
|
|
|
1587
1587
|
}
|
|
1588
1588
|
|
|
1589
1589
|
const restoredHistory = [...preRepairMessages, ...newMessages];
|
|
1590
|
-
ctx.messages = stripInjectedContext(restoredHistory);
|
|
1591
1590
|
|
|
1592
1591
|
const postLoopContextEstimate = estimatePromptTokens(
|
|
1593
|
-
|
|
1592
|
+
restoredHistory,
|
|
1594
1593
|
ctx.systemPrompt,
|
|
1595
1594
|
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
1596
1595
|
);
|
|
1597
1596
|
|
|
1597
|
+
ctx.messages = stripInjectedContext(restoredHistory);
|
|
1598
|
+
|
|
1598
1599
|
emitUsage(
|
|
1599
1600
|
ctx,
|
|
1600
1601
|
state.exchangeInputTokens,
|
|
@@ -42,7 +42,10 @@ import {
|
|
|
42
42
|
removeSkillsIndexEntry,
|
|
43
43
|
validateManagedSkillId,
|
|
44
44
|
} from "../../skills/managed-store.js";
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
deleteSkillCapabilityMemory,
|
|
47
|
+
seedCatalogSkillMemories,
|
|
48
|
+
} from "../../skills/skill-memory.js";
|
|
46
49
|
import { getWorkspaceSkillsDir } from "../../util/platform.js";
|
|
47
50
|
import {
|
|
48
51
|
CONFIG_RELOAD_DEBOUNCE_MS,
|
|
@@ -478,6 +481,7 @@ export function enableSkill(
|
|
|
478
481
|
name: skillId,
|
|
479
482
|
state: "enabled",
|
|
480
483
|
});
|
|
484
|
+
seedCatalogSkillMemories();
|
|
481
485
|
return { success: true };
|
|
482
486
|
} catch (err) {
|
|
483
487
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -572,6 +576,7 @@ export async function installSkill(
|
|
|
572
576
|
"Failed to auto-enable bundled skill",
|
|
573
577
|
);
|
|
574
578
|
}
|
|
579
|
+
seedCatalogSkillMemories();
|
|
575
580
|
return { success: true };
|
|
576
581
|
}
|
|
577
582
|
|
|
@@ -602,6 +607,7 @@ export async function installSkill(
|
|
|
602
607
|
);
|
|
603
608
|
}
|
|
604
609
|
|
|
610
|
+
seedCatalogSkillMemories();
|
|
605
611
|
return { success: true };
|
|
606
612
|
}
|
|
607
613
|
} catch (err) {
|
|
@@ -637,6 +643,7 @@ export async function installSkill(
|
|
|
637
643
|
log.warn({ err, skillId }, "Failed to auto-enable installed skill");
|
|
638
644
|
}
|
|
639
645
|
|
|
646
|
+
seedCatalogSkillMemories();
|
|
640
647
|
return { success: true };
|
|
641
648
|
} catch (err) {
|
|
642
649
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1035,6 +1042,7 @@ export async function createSkill(
|
|
|
1035
1042
|
);
|
|
1036
1043
|
}
|
|
1037
1044
|
|
|
1045
|
+
seedCatalogSkillMemories();
|
|
1038
1046
|
return { success: true };
|
|
1039
1047
|
} catch (err) {
|
|
1040
1048
|
const message = err instanceof Error ? err.message : String(err);
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { reconcileCallsOnStartup } from "../calls/call-recovery.js";
|
|
|
5
5
|
import { setRelayBroadcast } from "../calls/relay-server.js";
|
|
6
6
|
import { TwilioConversationRelayProvider } from "../calls/twilio-provider.js";
|
|
7
7
|
import { setVoiceBridgeDeps } from "../calls/voice-session-bridge.js";
|
|
8
|
+
import { seedCliCommandMemories } from "../cli/cli-memory.js";
|
|
8
9
|
import {
|
|
9
10
|
getPlatformAssistantId,
|
|
10
11
|
getQdrantHttpPortEnv,
|
|
@@ -641,11 +642,19 @@ export async function runDaemon(): Promise<void> {
|
|
|
641
642
|
log.info("Daemon startup: starting memory worker");
|
|
642
643
|
bgRefs.memoryWorker = startMemoryJobsWorker();
|
|
643
644
|
|
|
644
|
-
// Seed capability memories for
|
|
645
|
+
// Seed capability memories for all enabled skills so the memory
|
|
645
646
|
// pipeline can surface relevant skills via semantic search.
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
)
|
|
647
|
+
try {
|
|
648
|
+
seedCatalogSkillMemories();
|
|
649
|
+
} catch (err) {
|
|
650
|
+
log.warn({ err }, "Catalog skill memory seeding failed — continuing");
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
seedCliCommandMemories();
|
|
655
|
+
} catch (err) {
|
|
656
|
+
log.warn({ err }, "CLI command memory seeding failed — continuing");
|
|
657
|
+
}
|
|
649
658
|
}
|
|
650
659
|
|
|
651
660
|
// Fire-and-forget: Qdrant init runs concurrently with the rest of startup
|
|
@@ -8,6 +8,7 @@ import type { AssistantConfig } from "../config/types.js";
|
|
|
8
8
|
import { setSentryOrganizationId, setSentryUserId } from "../instrument.js";
|
|
9
9
|
import { getMcpServerManager } from "../mcp/manager.js";
|
|
10
10
|
import { gmailMessagingProvider } from "../messaging/providers/gmail/adapter.js";
|
|
11
|
+
import { outlookMessagingProvider } from "../messaging/providers/outlook/adapter.js";
|
|
11
12
|
import { slackProvider as slackMessagingProvider } from "../messaging/providers/slack/adapter.js";
|
|
12
13
|
import { telegramBotMessagingProvider } from "../messaging/providers/telegram-bot/adapter.js";
|
|
13
14
|
import { whatsappMessagingProvider } from "../messaging/providers/whatsapp/adapter.js";
|
|
@@ -140,6 +141,7 @@ export function registerWatcherProviders(): void {
|
|
|
140
141
|
export function registerMessagingProviders(): void {
|
|
141
142
|
registerMessagingProvider(slackMessagingProvider);
|
|
142
143
|
registerMessagingProvider(gmailMessagingProvider);
|
|
144
|
+
registerMessagingProvider(outlookMessagingProvider);
|
|
143
145
|
registerMessagingProvider(telegramBotMessagingProvider);
|
|
144
146
|
registerMessagingProvider(whatsappMessagingProvider);
|
|
145
147
|
}
|
package/src/daemon/server.ts
CHANGED
|
@@ -576,6 +576,17 @@ export class DaemonServer {
|
|
|
576
576
|
if (params.attachments && params.attachments.length > 0) {
|
|
577
577
|
for (const a of params.attachments) {
|
|
578
578
|
try {
|
|
579
|
+
const validation = attachmentsStore.validateAttachmentUpload(
|
|
580
|
+
a.filename,
|
|
581
|
+
a.mimeType,
|
|
582
|
+
);
|
|
583
|
+
if (!validation.ok) {
|
|
584
|
+
log.warn(
|
|
585
|
+
{ error: validation.error, path: a.path },
|
|
586
|
+
"Signal attachment rejected by validation",
|
|
587
|
+
);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
579
590
|
const size = statSync(a.path).size;
|
|
580
591
|
const stored = attachmentsStore.uploadFileBackedAttachment(
|
|
581
592
|
a.filename,
|
|
@@ -584,12 +595,11 @@ export class DaemonServer {
|
|
|
584
595
|
size,
|
|
585
596
|
);
|
|
586
597
|
attachmentIds.push(stored.id);
|
|
587
|
-
const fileData = readFileSync(a.path).toString("base64");
|
|
588
598
|
resolvedAttachments.push({
|
|
589
599
|
id: stored.id,
|
|
590
600
|
filename: a.filename,
|
|
591
601
|
mimeType: a.mimeType,
|
|
592
|
-
data:
|
|
602
|
+
data: "",
|
|
593
603
|
filePath: a.path,
|
|
594
604
|
});
|
|
595
605
|
} catch (err) {
|
|
@@ -609,20 +619,17 @@ export class DaemonServer {
|
|
|
609
619
|
"string"
|
|
610
620
|
? (msg as { conversationId: string }).conversationId
|
|
611
621
|
: undefined;
|
|
612
|
-
|
|
613
|
-
DAEMON_INTERNAL_ASSISTANT_ID,
|
|
614
|
-
msg,
|
|
615
|
-
msgConversationId ?? conversationId,
|
|
616
|
-
);
|
|
617
|
-
assistantEventHub.publish(event).catch((err) => {
|
|
618
|
-
log.warn(
|
|
619
|
-
{ err },
|
|
620
|
-
"assistant-events hub subscriber threw during signal user-message",
|
|
621
|
-
);
|
|
622
|
-
});
|
|
622
|
+
this.publishAssistantEvent(msg, msgConversationId ?? conversationId);
|
|
623
623
|
};
|
|
624
624
|
|
|
625
625
|
if (conversation.isProcessing()) {
|
|
626
|
+
// Hydrate file data now — the queue path won't re-read from
|
|
627
|
+
// the attachment store, so base64 content must be inline.
|
|
628
|
+
for (const att of resolvedAttachments) {
|
|
629
|
+
if (att.filePath && !att.data) {
|
|
630
|
+
att.data = readFileSync(att.filePath).toString("base64");
|
|
631
|
+
}
|
|
632
|
+
}
|
|
626
633
|
const requestId = crypto.randomUUID();
|
|
627
634
|
const resolvedChannel = resolveTurnChannel(params.sourceChannel);
|
|
628
635
|
const resolvedInterface = resolveTurnInterface(params.sourceInterface);
|
|
@@ -977,15 +984,12 @@ export class DaemonServer {
|
|
|
977
984
|
// Persist the conversation's current trust/auth context so it survives
|
|
978
985
|
// eviction and recreation. The restore path in getOrCreateConversation
|
|
979
986
|
// reads from storedOptions.trustContext / storedOptions.authContext.
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
...(currentAuth ? { authContext: currentAuth } : {}),
|
|
987
|
-
});
|
|
988
|
-
}
|
|
987
|
+
// Always write — including null — so explicit clearing isn't lost.
|
|
988
|
+
this.conversationOptions.set(conversationId, {
|
|
989
|
+
...this.conversationOptions.get(conversationId),
|
|
990
|
+
trustContext: conversation.trustContext,
|
|
991
|
+
authContext: conversation.authContext,
|
|
992
|
+
});
|
|
989
993
|
conversation.setChannelCapabilities(
|
|
990
994
|
resolveChannelCapabilities(
|
|
991
995
|
sourceChannel,
|