@vellumai/assistant 0.4.45 → 0.4.48
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 +6 -6
- package/docs/architecture/memory.md +1 -1
- package/docs/architecture/scheduling.md +2 -3
- package/docs/architecture/security.md +5 -5
- package/docs/trusted-contact-access.md +5 -6
- package/package.json +4 -1
- package/src/__tests__/avatar-e2e.test.ts +18 -219
- package/src/__tests__/avatar-generator.test.ts +5 -57
- package/src/__tests__/browser-fill-credential.test.ts +5 -2
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
- package/src/__tests__/channel-readiness-routes.test.ts +20 -19
- package/src/__tests__/cli.test.ts +23 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
- package/src/__tests__/credential-broker-server-use.test.ts +22 -21
- package/src/__tests__/credential-broker.test.ts +2 -1
- package/src/__tests__/credential-metadata-store.test.ts +240 -18
- package/src/__tests__/credential-resolve.test.ts +5 -4
- package/src/__tests__/credential-security-e2e.test.ts +8 -8
- package/src/__tests__/credential-security-invariants.test.ts +104 -7
- package/src/__tests__/credential-vault-unit.test.ts +22 -20
- package/src/__tests__/credential-vault.test.ts +284 -12
- package/src/__tests__/credentials-cli.test.ts +11 -6
- package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
- package/src/__tests__/gemini-image-service.test.ts +75 -45
- package/src/__tests__/gemini-provider.test.ts +9 -6
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
- package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
- package/src/__tests__/guardian-grant-minting.test.ts +35 -0
- package/src/__tests__/integration-status.test.ts +53 -21
- package/src/__tests__/managed-proxy-context.test.ts +5 -3
- package/src/__tests__/media-generate-image.test.ts +63 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
- package/src/__tests__/messaging-send-tool.test.ts +4 -6
- package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
- package/src/__tests__/schedule-store.test.ts +1 -1
- package/src/__tests__/schema-transforms.test.ts +226 -0
- package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
- package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +5 -3
- package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +0 -9
- package/src/__tests__/slack-channel-config.test.ts +9 -8
- package/src/__tests__/slack-share-routes.test.ts +11 -6
- package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
- package/src/__tests__/twilio-config.test.ts +2 -1
- package/src/__tests__/twilio-provider.test.ts +4 -2
- package/src/__tests__/twilio-routes.test.ts +5 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
- package/src/approvals/AGENTS.md +1 -1
- package/src/calls/call-domain.ts +7 -4
- package/src/calls/twilio-config.ts +2 -1
- package/src/calls/twilio-provider.ts +2 -1
- package/src/calls/twilio-rest.ts +2 -2
- package/src/cli/commands/browser-relay.ts +40 -15
- package/src/cli/commands/credentials.ts +9 -8
- package/src/cli/commands/oauth.ts +1 -1
- package/src/cli.ts +3 -2
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
- package/src/config/bundled-skills/gmail/SKILL.md +4 -4
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
- package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +70 -29
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
- package/src/config/bundled-skills/messaging/SKILL.md +6 -6
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +11 -11
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +5 -5
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
- package/src/config/loader.ts +6 -0
- package/src/daemon/computer-use-session.ts +7 -1
- package/src/daemon/guardian-action-generators.ts +4 -5
- package/src/daemon/handlers/config-slack-channel.ts +37 -20
- package/src/daemon/handlers/config-telegram.ts +33 -20
- package/src/daemon/lifecycle.ts +9 -1
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/ride-shotgun-handler.ts +3 -1
- package/src/daemon/session-messaging.ts +3 -1
- package/src/daemon/session-tool-setup.ts +18 -2
- package/src/daemon/session.ts +1 -1
- package/src/email/providers/index.ts +2 -1
- package/src/instrument.ts +15 -1
- package/src/media/app-icon-generator.ts +30 -4
- package/src/media/avatar-router.ts +28 -62
- package/src/media/gemini-image-service.ts +28 -2
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/guardian-action-store.ts +1 -1
- package/src/memory/schema/guardian.ts +1 -1
- package/src/messaging/provider.ts +16 -10
- package/src/messaging/providers/gmail/adapter.ts +40 -23
- package/src/messaging/providers/gmail/client.ts +203 -122
- package/src/messaging/providers/gmail/people-client.ts +26 -18
- package/src/messaging/providers/slack/adapter.ts +29 -19
- package/src/messaging/providers/slack/client.ts +265 -78
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -4
- package/src/messaging/providers/whatsapp/adapter.ts +6 -3
- package/src/messaging/registry.ts +2 -1
- package/src/oauth/byo-connection.test.ts +436 -0
- package/src/oauth/byo-connection.ts +112 -0
- package/src/oauth/connect-orchestrator.ts +27 -0
- package/src/oauth/connection-resolver.ts +34 -0
- package/src/oauth/connection.ts +38 -0
- package/src/oauth/platform-connection.test.ts +163 -0
- package/src/oauth/platform-connection.ts +110 -0
- package/src/oauth/provider-base-urls.ts +21 -0
- package/src/oauth/provider-profiles.ts +1 -1
- package/src/oauth/token-persistence.ts +20 -20
- package/src/permissions/checker.ts +6 -1
- package/src/prompts/system-prompt.ts +52 -15
- package/src/prompts/templates/BOOTSTRAP.md +1 -1
- package/src/providers/gemini/client.ts +15 -6
- package/src/providers/managed-proxy/constants.ts +2 -2
- package/src/providers/managed-proxy/context.ts +5 -1
- package/src/providers/ratelimit.ts +17 -0
- package/src/providers/registry.ts +2 -2
- package/src/runtime/AGENTS.md +18 -1
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +2 -1
- package/src/runtime/channel-readiness-service.ts +168 -195
- package/src/runtime/channel-readiness-types.ts +4 -0
- package/src/runtime/guardian-action-conversation-turn.ts +1 -3
- package/src/runtime/guardian-action-followup-executor.ts +1 -2
- package/src/runtime/guardian-action-message-composer.ts +3 -23
- package/src/runtime/http-server.ts +9 -4
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/middleware/rate-limiter.ts +74 -20
- package/src/runtime/middleware/twilio-validation.ts +1 -3
- package/src/runtime/routes/channel-readiness-routes.ts +2 -0
- package/src/runtime/routes/diagnostics-routes.ts +11 -9
- package/src/runtime/routes/guardian-approval-interception.ts +20 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +71 -25
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -5
- package/src/runtime/routes/integrations/slack/share.ts +3 -2
- package/src/runtime/routes/integrations/twilio.ts +6 -5
- package/src/runtime/routes/secret-routes.ts +3 -2
- package/src/runtime/routes/settings-routes.ts +75 -17
- package/src/runtime/telegram-streaming-delivery.test.ts +132 -0
- package/src/runtime/telegram-streaming-delivery.ts +11 -1
- package/src/schedule/integration-status.ts +5 -4
- package/src/security/credential-key.ts +170 -0
- package/src/security/token-manager.ts +36 -7
- package/src/tools/apps/definitions.ts +0 -5
- package/src/tools/assets/materialize.ts +0 -5
- package/src/tools/assets/search.ts +0 -5
- package/src/tools/browser/headless-browser.ts +1 -67
- package/src/tools/claude-code/claude-code.ts +0 -5
- package/src/tools/computer-use/request-computer-control.ts +0 -5
- package/src/tools/credentials/broker.ts +6 -4
- package/src/tools/credentials/metadata-store.ts +72 -20
- package/src/tools/credentials/resolve.ts +2 -1
- package/src/tools/credentials/vault.ts +77 -16
- package/src/tools/filesystem/edit.ts +1 -6
- package/src/tools/filesystem/read.ts +0 -5
- package/src/tools/filesystem/write.ts +1 -6
- package/src/tools/host-filesystem/edit.ts +1 -6
- package/src/tools/host-filesystem/read.ts +1 -6
- package/src/tools/host-filesystem/write.ts +1 -6
- package/src/tools/mcp/mcp-tool-factory.ts +18 -1
- package/src/tools/memory/definitions.ts +0 -5
- package/src/tools/network/web-fetch.ts +0 -5
- package/src/tools/network/web-search.ts +0 -5
- package/src/tools/schema-transforms.ts +99 -0
- package/src/tools/skills/load.ts +0 -5
- package/src/tools/swarm/delegate.ts +0 -5
- package/src/tools/system/avatar-generator.ts +3 -44
- package/src/tools/ui-surface/definitions.ts +0 -15
- package/src/tools/watch/screen-watch.ts +0 -5
- package/src/version.ts +10 -0
- package/src/watcher/providers/github.ts +51 -52
- package/src/watcher/providers/gmail.ts +88 -80
- package/src/watcher/providers/google-calendar.ts +93 -86
- package/src/watcher/providers/linear.ts +87 -93
- package/src/__tests__/avatar-router.test.ts +0 -149
- package/src/__tests__/managed-avatar-client.test.ts +0 -337
- package/src/config/bundled-skills/doordash/SKILL.md +0 -170
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +0 -205
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -74
- package/src/config/bundled-skills/doordash/doordash-cli.ts +0 -1081
- package/src/config/bundled-skills/doordash/doordash-entry.ts +0 -22
- package/src/config/bundled-skills/doordash/lib/cart-queries.ts +0 -787
- package/src/config/bundled-skills/doordash/lib/client.ts +0 -1069
- package/src/config/bundled-skills/doordash/lib/order-queries.ts +0 -85
- package/src/config/bundled-skills/doordash/lib/queries.ts +0 -28
- package/src/config/bundled-skills/doordash/lib/query-extractor.ts +0 -94
- package/src/config/bundled-skills/doordash/lib/search-queries.ts +0 -203
- package/src/config/bundled-skills/doordash/lib/session.ts +0 -96
- package/src/config/bundled-skills/doordash/lib/shared/errors.ts +0 -61
- package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +0 -380
- package/src/config/bundled-skills/doordash/lib/shared/platform.ts +0 -55
- package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +0 -43
- package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +0 -49
- package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +0 -6
- package/src/config/bundled-skills/doordash/lib/store-queries.ts +0 -246
- package/src/config/bundled-skills/doordash/lib/types.ts +0 -367
- package/src/media/avatar-types.ts +0 -53
- package/src/media/managed-avatar-client.ts +0 -225
|
@@ -2,7 +2,7 @@ import type {
|
|
|
2
2
|
ToolContext,
|
|
3
3
|
ToolExecutionResult,
|
|
4
4
|
} from "../../../../tools/types.js";
|
|
5
|
-
import { err, ok, resolveProvider
|
|
5
|
+
import { err, getProviderConnection, ok, resolveProvider } from "./shared.js";
|
|
6
6
|
|
|
7
7
|
export async function run(
|
|
8
8
|
input: Record<string, unknown>,
|
|
@@ -23,47 +23,46 @@ export async function run(
|
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (result.senders.length === 0) {
|
|
34
|
-
return ok(
|
|
35
|
-
JSON.stringify({
|
|
36
|
-
senders: [],
|
|
37
|
-
total_scanned: result.totalScanned,
|
|
38
|
-
query_used: result.queryUsed,
|
|
39
|
-
...(result.truncated ? { truncated: true } : {}),
|
|
40
|
-
message:
|
|
41
|
-
"No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).",
|
|
42
|
-
}),
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Map to snake_case output format for LLM consumption
|
|
47
|
-
const senders = result.senders.map((s) => ({
|
|
48
|
-
id: s.id,
|
|
49
|
-
display_name: s.displayName,
|
|
50
|
-
email: s.email,
|
|
51
|
-
message_count: s.messageCount,
|
|
52
|
-
has_unsubscribe: s.hasUnsubscribe,
|
|
53
|
-
newest_message_id: s.newestMessageId,
|
|
54
|
-
search_query: s.searchQuery,
|
|
55
|
-
}));
|
|
26
|
+
const conn = getProviderConnection(provider);
|
|
27
|
+
const result = await provider.senderDigest!(conn, query, {
|
|
28
|
+
maxMessages,
|
|
29
|
+
maxSenders,
|
|
30
|
+
pageToken,
|
|
31
|
+
});
|
|
56
32
|
|
|
33
|
+
if (result.senders.length === 0) {
|
|
57
34
|
return ok(
|
|
58
35
|
JSON.stringify({
|
|
59
|
-
senders,
|
|
36
|
+
senders: [],
|
|
60
37
|
total_scanned: result.totalScanned,
|
|
61
38
|
query_used: result.queryUsed,
|
|
62
39
|
...(result.truncated ? { truncated: true } : {}),
|
|
63
|
-
|
|
40
|
+
message:
|
|
41
|
+
"No emails found matching the query. Try broadening the search (e.g. remove category filter or extend date range).",
|
|
64
42
|
}),
|
|
65
43
|
);
|
|
66
|
-
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Map to snake_case output format for LLM consumption
|
|
47
|
+
const senders = result.senders.map((s) => ({
|
|
48
|
+
id: s.id,
|
|
49
|
+
display_name: s.displayName,
|
|
50
|
+
email: s.email,
|
|
51
|
+
message_count: s.messageCount,
|
|
52
|
+
has_unsubscribe: s.hasUnsubscribe,
|
|
53
|
+
newest_message_id: s.newestMessageId,
|
|
54
|
+
search_query: s.searchQuery,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
return ok(
|
|
58
|
+
JSON.stringify({
|
|
59
|
+
senders,
|
|
60
|
+
total_scanned: result.totalScanned,
|
|
61
|
+
query_used: result.queryUsed,
|
|
62
|
+
...(result.truncated ? { truncated: true } : {}),
|
|
63
|
+
note: `message_count reflects emails found per sender within the ${result.totalScanned} messages scanned. Use messaging_archive_by_sender with the sender's search_query to archive their messages.`,
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
67
66
|
} catch (e) {
|
|
68
67
|
return err(e instanceof Error ? e.message : String(e));
|
|
69
68
|
}
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
getMessagingProvider,
|
|
9
9
|
isPlatformEnabled,
|
|
10
10
|
} from "../../../../messaging/registry.js";
|
|
11
|
-
import {
|
|
11
|
+
import type { OAuthConnection } from "../../../../oauth/connection.js";
|
|
12
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
12
13
|
import type { ToolExecutionResult } from "../../../../tools/types.js";
|
|
13
14
|
|
|
14
15
|
export function ok(content: string): ToolExecutionResult {
|
|
@@ -126,16 +127,15 @@ export function resolveProvider(platformInput?: string): MessagingProvider {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
130
|
+
* Resolve an OAuthConnection (or empty string for non-OAuth providers)
|
|
131
|
+
* for the given messaging provider.
|
|
132
|
+
*
|
|
133
|
+
* Non-OAuth providers (e.g. Telegram) use isConnected() and don't need
|
|
134
|
+
* tokens — they receive an empty string which the string overload handles.
|
|
132
135
|
*/
|
|
133
|
-
export
|
|
136
|
+
export function getProviderConnection(
|
|
134
137
|
provider: MessagingProvider,
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
return fn("");
|
|
139
|
-
}
|
|
140
|
-
return withValidToken(provider.credentialService, fn);
|
|
138
|
+
): OAuthConnection | string {
|
|
139
|
+
if (provider.isConnected?.()) return "";
|
|
140
|
+
return resolveOAuthConnection(provider.credentialService);
|
|
141
141
|
}
|
|
@@ -38,4 +38,4 @@ Thread grouping is handled by the LLM-powered decision engine, not by any parame
|
|
|
38
38
|
## Important
|
|
39
39
|
|
|
40
40
|
- Do **NOT** use AppleScript `display notification` or other OS-level notification commands for assistant-managed alerts. Always use `send_notification`.
|
|
41
|
-
- For sending rich content (digests, summaries, reports) to a specific chat
|
|
41
|
+
- For sending rich content (digests, summaries, reports) to a specific chat or email destination, use the messaging skill's `messaging_send` instead. The decision engine rewrites `send_notification` content into short alerts, which strips rich formatting.
|
|
@@ -147,10 +147,10 @@ During an active call, the user can interact with the AI voice agent via the HTT
|
|
|
147
147
|
|
|
148
148
|
#### Answering questions
|
|
149
149
|
|
|
150
|
-
When the AI voice agent encounters something it needs user input for, it dispatches an **ASK_GUARDIAN** request to all configured guardian channels (mac desktop, Telegram
|
|
150
|
+
When the AI voice agent encounters something it needs user input for, it dispatches an **ASK_GUARDIAN** request to all configured guardian channels (mac desktop, Telegram). The call status changes to `waiting_on_user`.
|
|
151
151
|
|
|
152
152
|
1. The question is delivered simultaneously to every configured channel. The first channel to respond wins (first-response-wins semantics) -- once one channel provides an answer, the other channels receive a "already answered" notice.
|
|
153
|
-
2. On the mac desktop, a guardian request thread is created with the question. On Telegram
|
|
153
|
+
2. On the mac desktop, a guardian request thread is created with the question. On Telegram, the question text and a request code are delivered via the gateway.
|
|
154
154
|
3. If DTMF callee verification is enabled, the callee must enter a verification code before the call proceeds (see the **DTMF Callee Verification** section above).
|
|
155
155
|
4. The guardian provides an answer through whichever channel they prefer. The answer is routed to the AI voice agent, which continues the conversation naturally.
|
|
156
156
|
|
|
@@ -160,16 +160,16 @@ When the AI voice agent encounters something it needs user input for, it dispatc
|
|
|
160
160
|
|
|
161
161
|
When a consultation times out, the voice agent apologizes to the caller and moves on -- but the interaction is not lost. If the guardian responds after the timeout:
|
|
162
162
|
|
|
163
|
-
1. **Late reply detection**: The system recognizes the late answer on whichever channel it arrives (desktop
|
|
163
|
+
1. **Late reply detection**: The system recognizes the late answer on whichever channel it arrives (desktop or Telegram) and presents a follow-up prompt asking the guardian what they would like to do.
|
|
164
164
|
2. **Follow-up options**: The guardian can choose to:
|
|
165
165
|
- **Call back** the original caller with the answer
|
|
166
166
|
- **Send a text message** to the caller with the answer
|
|
167
167
|
- **Decline** if the follow-up is no longer needed
|
|
168
|
-
3. **Automatic execution**: If the guardian chooses to call back or send a message, the system resolves the original caller's phone number from the call record and executes the action automatically -- placing an outbound callback call or sending
|
|
168
|
+
3. **Automatic execution**: If the guardian chooses to call back or send a message, the system resolves the original caller's phone number from the call record and executes the action automatically -- placing an outbound callback call or sending a message via the gateway.
|
|
169
169
|
|
|
170
170
|
All user-facing messages in this flow (timeout acknowledgments, follow-up prompts, completion confirmations) are generated by the assistant to maintain a natural, conversational tone. No fixed/canned responses are used.
|
|
171
171
|
|
|
172
|
-
The follow-up flow works across all guardian channels. The guardian can receive the timeout notice on Telegram
|
|
172
|
+
The follow-up flow works across all guardian channels. The guardian can receive the timeout notice on Telegram and choose to call back -- the system handles cross-channel routing transparently.
|
|
173
173
|
|
|
174
174
|
#### Steering with instructions
|
|
175
175
|
|
|
@@ -147,7 +147,7 @@ Before confirming a schedule to the user, you MUST verify that you have the capa
|
|
|
147
147
|
When `schedule_create` returns, it includes an integration status summary. Cross-reference the scheduled task's requirements against the available integrations:
|
|
148
148
|
|
|
149
149
|
- If the task involves **email** (reading, sending, OTP verification): an email integration must be connected (check the "email" category)
|
|
150
|
-
- If the task involves **
|
|
150
|
+
- If the task involves **making calls**: Twilio must be connected
|
|
151
151
|
- If the task involves **web browsing or form-filling**: browser automation must be available (check client type)
|
|
152
152
|
- If the task involves a **multi-step workflow** (e.g., book appointment → read confirmation email), trace the full dependency chain
|
|
153
153
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Shared utilities for slack skill tools.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import type { OAuthConnection } from "../../../../oauth/connection.js";
|
|
6
|
+
import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
|
|
7
7
|
import type { ToolExecutionResult } from "../../../../tools/types.js";
|
|
8
8
|
|
|
9
9
|
export function ok(content: string): ToolExecutionResult {
|
|
@@ -14,12 +14,6 @@ export function err(message: string): ToolExecutionResult {
|
|
|
14
14
|
return { content: message, isError: true };
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
export async function withSlackToken<T>(
|
|
21
|
-
fn: (token: string) => Promise<T>,
|
|
22
|
-
): Promise<T> {
|
|
23
|
-
const provider = getMessagingProvider("slack");
|
|
24
|
-
return withValidToken(provider.credentialService, fn);
|
|
17
|
+
export function getSlackConnection(): OAuthConnection {
|
|
18
|
+
return resolveOAuthConnection("integration:slack");
|
|
25
19
|
}
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ToolContext,
|
|
4
4
|
ToolExecutionResult,
|
|
5
5
|
} from "../../../../tools/types.js";
|
|
6
|
-
import { err,
|
|
6
|
+
import { err, getSlackConnection, ok } from "./shared.js";
|
|
7
7
|
|
|
8
8
|
export async function run(
|
|
9
9
|
input: Record<string, unknown>,
|
|
@@ -18,10 +18,9 @@ export async function run(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
21
|
+
const connection = getSlackConnection();
|
|
22
|
+
await addReaction(connection, channel, timestamp, emoji);
|
|
23
|
+
return ok(`Added :${emoji}: reaction.`);
|
|
25
24
|
} catch (e) {
|
|
26
25
|
return err(e instanceof Error ? e.message : String(e));
|
|
27
26
|
}
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ToolContext,
|
|
4
4
|
ToolExecutionResult,
|
|
5
5
|
} from "../../../../tools/types.js";
|
|
6
|
-
import { err,
|
|
6
|
+
import { err, getSlackConnection, ok } from "./shared.js";
|
|
7
7
|
|
|
8
8
|
export async function run(
|
|
9
9
|
input: Record<string, unknown>,
|
|
@@ -16,23 +16,22 @@ export async function run(
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const connection = getSlackConnection();
|
|
20
|
+
const resp = await slack.conversationInfo(connection, channelId);
|
|
21
|
+
const conv = resp.channel;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
const result = {
|
|
24
|
+
channelId: conv.id,
|
|
25
|
+
name: conv.name ?? conv.id,
|
|
26
|
+
topic: conv.topic?.value || null,
|
|
27
|
+
purpose: conv.purpose?.value || null,
|
|
28
|
+
isPrivate: conv.is_private ?? conv.is_group ?? false,
|
|
29
|
+
isArchived: conv.is_archived ?? false,
|
|
30
|
+
memberCount: conv.num_members ?? null,
|
|
31
|
+
latestActivityTs: conv.latest?.ts ?? null,
|
|
32
|
+
};
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
});
|
|
34
|
+
return ok(JSON.stringify(result, null, 2));
|
|
36
35
|
} catch (e) {
|
|
37
36
|
return err(e instanceof Error ? e.message : String(e));
|
|
38
37
|
}
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ToolContext,
|
|
4
4
|
ToolExecutionResult,
|
|
5
5
|
} from "../../../../tools/types.js";
|
|
6
|
-
import { err,
|
|
6
|
+
import { err, getSlackConnection, ok } from "./shared.js";
|
|
7
7
|
|
|
8
8
|
export async function run(
|
|
9
9
|
input: Record<string, unknown>,
|
|
@@ -17,10 +17,9 @@ export async function run(
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
20
|
+
const connection = getSlackConnection();
|
|
21
|
+
await deleteMessage(connection, channel, timestamp);
|
|
22
|
+
return ok(`Message deleted.`);
|
|
24
23
|
} catch (e) {
|
|
25
24
|
return err(e instanceof Error ? e.message : String(e));
|
|
26
25
|
}
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ToolContext,
|
|
4
4
|
ToolExecutionResult,
|
|
5
5
|
} from "../../../../tools/types.js";
|
|
6
|
-
import { err,
|
|
6
|
+
import { err, getSlackConnection, ok } from "./shared.js";
|
|
7
7
|
|
|
8
8
|
export async function run(
|
|
9
9
|
input: Record<string, unknown>,
|
|
@@ -18,10 +18,9 @@ export async function run(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
21
|
+
const connection = getSlackConnection();
|
|
22
|
+
await updateMessage(connection, channel, timestamp, text);
|
|
23
|
+
return ok(`Message updated.`);
|
|
25
24
|
} catch (e) {
|
|
26
25
|
return err(e instanceof Error ? e.message : String(e));
|
|
27
26
|
}
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ToolContext,
|
|
4
4
|
ToolExecutionResult,
|
|
5
5
|
} from "../../../../tools/types.js";
|
|
6
|
-
import { err,
|
|
6
|
+
import { err, getSlackConnection, ok } from "./shared.js";
|
|
7
7
|
|
|
8
8
|
export async function run(
|
|
9
9
|
input: Record<string, unknown>,
|
|
@@ -16,10 +16,9 @@ export async function run(
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
19
|
+
const connection = getSlackConnection();
|
|
20
|
+
await leaveConversation(connection, channel);
|
|
21
|
+
return ok("Left channel.");
|
|
23
22
|
} catch (e) {
|
|
24
23
|
return err(e instanceof Error ? e.message : String(e));
|
|
25
24
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { getConfig } from "../../../../config/loader.js";
|
|
2
2
|
import * as slack from "../../../../messaging/providers/slack/client.js";
|
|
3
3
|
import type { SlackConversation } from "../../../../messaging/providers/slack/types.js";
|
|
4
|
+
import type { OAuthConnection } from "../../../../oauth/connection.js";
|
|
4
5
|
import type {
|
|
5
6
|
ToolContext,
|
|
6
7
|
ToolExecutionResult,
|
|
7
8
|
} from "../../../../tools/types.js";
|
|
8
|
-
import { err,
|
|
9
|
+
import { err, getSlackConnection, ok } from "./shared.js";
|
|
9
10
|
|
|
10
11
|
interface ThreadSummary {
|
|
11
12
|
threadTs: string;
|
|
@@ -26,13 +27,16 @@ interface ChannelDigest {
|
|
|
26
27
|
|
|
27
28
|
const userNameCache = new Map<string, string>();
|
|
28
29
|
|
|
29
|
-
async function resolveUserName(
|
|
30
|
+
async function resolveUserName(
|
|
31
|
+
connection: OAuthConnection,
|
|
32
|
+
userId: string,
|
|
33
|
+
): Promise<string> {
|
|
30
34
|
if (!userId) return "unknown";
|
|
31
35
|
const cached = userNameCache.get(userId);
|
|
32
36
|
if (cached) return cached;
|
|
33
37
|
|
|
34
38
|
try {
|
|
35
|
-
const resp = await slack.userInfo(
|
|
39
|
+
const resp = await slack.userInfo(connection, userId);
|
|
36
40
|
const name =
|
|
37
41
|
resp.user.profile?.display_name ||
|
|
38
42
|
resp.user.profile?.real_name ||
|
|
@@ -46,7 +50,7 @@ async function resolveUserName(token: string, userId: string): Promise<string> {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
async function scanChannel(
|
|
49
|
-
|
|
53
|
+
connection: OAuthConnection,
|
|
50
54
|
conv: SlackConversation,
|
|
51
55
|
oldestTs: string,
|
|
52
56
|
includeThreads: boolean,
|
|
@@ -57,7 +61,7 @@ async function scanChannel(
|
|
|
57
61
|
|
|
58
62
|
try {
|
|
59
63
|
const history = await slack.conversationHistory(
|
|
60
|
-
|
|
64
|
+
connection,
|
|
61
65
|
channelId,
|
|
62
66
|
100,
|
|
63
67
|
undefined,
|
|
@@ -71,7 +75,7 @@ async function scanChannel(
|
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
const keyParticipants = await Promise.all(
|
|
74
|
-
[...participantIds].map((uid) => resolveUserName(
|
|
78
|
+
[...participantIds].map((uid) => resolveUserName(connection, uid)),
|
|
75
79
|
);
|
|
76
80
|
|
|
77
81
|
const threadMessages = messages
|
|
@@ -86,7 +90,7 @@ async function scanChannel(
|
|
|
86
90
|
if (includeThreads) {
|
|
87
91
|
try {
|
|
88
92
|
const replies = await slack.conversationReplies(
|
|
89
|
-
|
|
93
|
+
connection,
|
|
90
94
|
channelId,
|
|
91
95
|
msg.ts,
|
|
92
96
|
10,
|
|
@@ -97,11 +101,11 @@ async function scanChannel(
|
|
|
97
101
|
}
|
|
98
102
|
participants = await Promise.all(
|
|
99
103
|
[...threadParticipantIds].map((uid) =>
|
|
100
|
-
resolveUserName(
|
|
104
|
+
resolveUserName(connection, uid),
|
|
101
105
|
),
|
|
102
106
|
);
|
|
103
107
|
} catch {
|
|
104
|
-
participants = [await resolveUserName(
|
|
108
|
+
participants = [await resolveUserName(connection, msg.user ?? "")];
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -260,15 +264,34 @@ export async function run(
|
|
|
260
264
|
const format = (input.format as string) ?? "text";
|
|
261
265
|
|
|
262
266
|
try {
|
|
263
|
-
|
|
264
|
-
|
|
267
|
+
const connection = getSlackConnection();
|
|
268
|
+
const oldestTs = String((Date.now() - hoursBack * 60 * 60 * 1000) / 1000);
|
|
265
269
|
|
|
266
|
-
|
|
267
|
-
|
|
270
|
+
let channelsToScan: SlackConversation[];
|
|
271
|
+
let failedLookups = 0;
|
|
268
272
|
|
|
269
|
-
|
|
273
|
+
if (channelIds?.length) {
|
|
274
|
+
const results = await Promise.allSettled(
|
|
275
|
+
channelIds.map((id) => slack.conversationInfo(connection, id)),
|
|
276
|
+
);
|
|
277
|
+
channelsToScan = results
|
|
278
|
+
.filter(
|
|
279
|
+
(
|
|
280
|
+
r,
|
|
281
|
+
): r is PromiseFulfilledResult<
|
|
282
|
+
Awaited<ReturnType<typeof slack.conversationInfo>>
|
|
283
|
+
> => r.status === "fulfilled",
|
|
284
|
+
)
|
|
285
|
+
.map((r) => r.value.channel);
|
|
286
|
+
failedLookups = results.filter((r) => r.status === "rejected").length;
|
|
287
|
+
} else {
|
|
288
|
+
const config = getConfig();
|
|
289
|
+
const preferredIds = config.skills?.entries?.slack?.config
|
|
290
|
+
?.preferredChannels as string[] | undefined;
|
|
291
|
+
|
|
292
|
+
if (preferredIds?.length) {
|
|
270
293
|
const results = await Promise.allSettled(
|
|
271
|
-
|
|
294
|
+
preferredIds.map((id) => slack.conversationInfo(connection, id)),
|
|
272
295
|
);
|
|
273
296
|
channelsToScan = results
|
|
274
297
|
.filter(
|
|
@@ -281,89 +304,69 @@ export async function run(
|
|
|
281
304
|
.map((r) => r.value.channel);
|
|
282
305
|
failedLookups = results.filter((r) => r.status === "rejected").length;
|
|
283
306
|
} else {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
307
|
+
const allChannels: SlackConversation[] = [];
|
|
308
|
+
let cursor: string | undefined;
|
|
309
|
+
do {
|
|
310
|
+
const resp = await slack.listConversations(
|
|
311
|
+
connection,
|
|
312
|
+
"public_channel,private_channel",
|
|
313
|
+
true,
|
|
314
|
+
200,
|
|
315
|
+
cursor,
|
|
291
316
|
);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
)
|
|
300
|
-
.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
let cursor: string | undefined;
|
|
305
|
-
do {
|
|
306
|
-
const resp = await slack.listConversations(
|
|
307
|
-
token,
|
|
308
|
-
"public_channel,private_channel",
|
|
309
|
-
true,
|
|
310
|
-
200,
|
|
311
|
-
cursor,
|
|
312
|
-
);
|
|
313
|
-
allChannels.push(...resp.channels);
|
|
314
|
-
cursor = resp.response_metadata?.next_cursor || undefined;
|
|
315
|
-
} while (cursor);
|
|
316
|
-
|
|
317
|
-
channelsToScan = allChannels
|
|
318
|
-
.filter((c) => c.is_member)
|
|
319
|
-
.sort((a, b) => {
|
|
320
|
-
const aTs = a.latest?.ts ? parseFloat(a.latest.ts) : 0;
|
|
321
|
-
const bTs = b.latest?.ts ? parseFloat(b.latest.ts) : 0;
|
|
322
|
-
return bTs - aTs;
|
|
323
|
-
})
|
|
324
|
-
.slice(0, maxChannels);
|
|
325
|
-
}
|
|
317
|
+
allChannels.push(...resp.channels);
|
|
318
|
+
cursor = resp.response_metadata?.next_cursor || undefined;
|
|
319
|
+
} while (cursor);
|
|
320
|
+
|
|
321
|
+
channelsToScan = allChannels
|
|
322
|
+
.filter((c) => c.is_member)
|
|
323
|
+
.sort((a, b) => {
|
|
324
|
+
const aTs = a.latest?.ts ? parseFloat(a.latest.ts) : 0;
|
|
325
|
+
const bTs = b.latest?.ts ? parseFloat(b.latest.ts) : 0;
|
|
326
|
+
return bTs - aTs;
|
|
327
|
+
})
|
|
328
|
+
.slice(0, maxChannels);
|
|
326
329
|
}
|
|
330
|
+
}
|
|
327
331
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const digests: ChannelDigest[] = scanResults
|
|
335
|
-
.filter(
|
|
336
|
-
(r): r is PromiseFulfilledResult<ChannelDigest> =>
|
|
337
|
-
r.status === "fulfilled",
|
|
338
|
-
)
|
|
339
|
-
.map((r) => r.value)
|
|
340
|
-
.filter((d) => d.messageCount > 0 || d.error);
|
|
341
|
-
|
|
342
|
-
const skippedCount = scanResults.filter(
|
|
343
|
-
(r) => r.status === "rejected",
|
|
344
|
-
).length;
|
|
345
|
-
|
|
346
|
-
if (format === "blocks") {
|
|
347
|
-
const blocks = buildBlockKitOutput(
|
|
348
|
-
digests,
|
|
349
|
-
hoursBack,
|
|
350
|
-
channelsToScan.length,
|
|
351
|
-
skippedCount,
|
|
352
|
-
);
|
|
353
|
-
return ok(JSON.stringify({ blocks }, null, 2));
|
|
354
|
-
}
|
|
332
|
+
const scanResults = await Promise.allSettled(
|
|
333
|
+
channelsToScan.map((conv) =>
|
|
334
|
+
scanChannel(connection, conv, oldestTs, includeThreads),
|
|
335
|
+
),
|
|
336
|
+
);
|
|
355
337
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
338
|
+
const digests: ChannelDigest[] = scanResults
|
|
339
|
+
.filter(
|
|
340
|
+
(r): r is PromiseFulfilledResult<ChannelDigest> =>
|
|
341
|
+
r.status === "fulfilled",
|
|
342
|
+
)
|
|
343
|
+
.map((r) => r.value)
|
|
344
|
+
.filter((d) => d.messageCount > 0 || d.error);
|
|
345
|
+
|
|
346
|
+
const skippedCount = scanResults.filter(
|
|
347
|
+
(r) => r.status === "rejected",
|
|
348
|
+
).length;
|
|
349
|
+
|
|
350
|
+
if (format === "blocks") {
|
|
351
|
+
const blocks = buildBlockKitOutput(
|
|
352
|
+
digests,
|
|
361
353
|
hoursBack,
|
|
362
|
-
|
|
363
|
-
|
|
354
|
+
channelsToScan.length,
|
|
355
|
+
skippedCount,
|
|
356
|
+
);
|
|
357
|
+
return ok(JSON.stringify({ blocks }, null, 2));
|
|
358
|
+
}
|
|
364
359
|
|
|
365
|
-
|
|
366
|
-
|
|
360
|
+
const result = {
|
|
361
|
+
scannedChannels: digests.length,
|
|
362
|
+
totalChannelsAttempted: channelsToScan.length,
|
|
363
|
+
skippedDueToErrors: skippedCount,
|
|
364
|
+
failedLookups,
|
|
365
|
+
hoursBack,
|
|
366
|
+
channels: digests,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
return ok(JSON.stringify(result, null, 2));
|
|
367
370
|
} catch (e) {
|
|
368
371
|
return err(e instanceof Error ? e.message : String(e));
|
|
369
372
|
}
|
package/src/config/loader.ts
CHANGED
|
@@ -333,6 +333,12 @@ export function loadConfig(): AssistantConfig {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// Environment variables override everything
|
|
336
|
+
if (process.env.VELLUM_CONFIG_SANDBOX_ENABLED === "false") {
|
|
337
|
+
config.sandbox.enabled = false;
|
|
338
|
+
} else if (process.env.VELLUM_CONFIG_SANDBOX_ENABLED === "true") {
|
|
339
|
+
config.sandbox.enabled = true;
|
|
340
|
+
}
|
|
341
|
+
|
|
336
342
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
337
343
|
config.apiKeys.anthropic = process.env.ANTHROPIC_API_KEY;
|
|
338
344
|
}
|