@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.
- package/ARCHITECTURE.md +39 -37
- package/README.md +5 -6
- package/docs/runbook-trusted-contacts.md +79 -43
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
- package/scripts/test.sh +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
- package/src/__tests__/actor-token-service.test.ts +4 -3
- package/src/__tests__/app-executors.test.ts +7 -17
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
- package/src/__tests__/browser-skill-endstate.test.ts +10 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +44 -44
- package/src/__tests__/channel-approval.test.ts +8 -0
- package/src/__tests__/channel-approvals.test.ts +39 -1
- package/src/__tests__/channel-guardian.test.ts +15 -5
- package/src/__tests__/channel-reply-delivery.test.ts +31 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +4 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/guardian-grant-minting.test.ts +6 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +34 -11
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
- package/src/__tests__/integrations-cli.test.ts +3 -27
- package/src/__tests__/intent-routing.test.ts +3 -0
- package/src/__tests__/invite-redemption-service.test.ts +1 -1
- package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
- package/src/__tests__/ipc-snapshot.test.ts +4 -31
- package/src/__tests__/nl-approval-parser.test.ts +305 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
- package/src/__tests__/provider-error-scenarios.test.ts +68 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/retry-after-extraction.test.ts +111 -0
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
- package/src/__tests__/skill-feature-flags.test.ts +18 -12
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
- package/src/__tests__/slack-block-formatting.test.ts +100 -0
- package/src/__tests__/slack-inbound-verification.test.ts +346 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
- package/src/__tests__/slack-skill.test.ts +3 -2
- package/src/__tests__/starter-task-flow.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +1 -1
- package/src/amazon/client.ts +7 -24
- package/src/calls/relay-server.ts +39 -11
- package/src/channels/config.ts +1 -1
- package/src/cli/integrations.ts +10 -66
- package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
- package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
- package/src/config/bundled-skills/browser/TOOLS.json +59 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
- package/src/config/bundled-skills/contacts/SKILL.md +42 -35
- package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +38 -58
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +11 -31
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +19 -37
- package/src/config/bundled-skills/document/TOOLS.json +8 -0
- package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
- package/src/config/bundled-skills/followups/TOOLS.json +12 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
- package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
- package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
- package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
- package/src/config/bundled-skills/notifications/SKILL.md +3 -2
- package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
- package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
- package/src/config/bundled-skills/schedule/SKILL.md +33 -15
- package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
- package/src/config/bundled-skills/slack/SKILL.md +30 -1
- package/src/config/bundled-skills/slack/TOOLS.json +89 -2
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
- package/src/config/bundled-skills/weather/TOOLS.json +4 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/env.ts +4 -1
- package/src/contacts/contact-store.ts +195 -4
- package/src/contacts/types.ts +26 -0
- package/src/daemon/assistant-attachments.ts +23 -3
- package/src/daemon/guardian-verification-intent.ts +7 -4
- package/src/daemon/handlers/apps.ts +1 -2
- package/src/daemon/handlers/config-inbox.ts +16 -134
- package/src/daemon/handlers/guardian-actions.ts +20 -87
- package/src/daemon/handlers/sessions.ts +0 -1
- package/src/daemon/ipc-contract/apps.ts +0 -1
- package/src/daemon/ipc-contract/inbox.ts +7 -66
- package/src/daemon/ipc-contract/sessions.ts +1 -0
- package/src/daemon/ipc-contract/surfaces.ts +0 -1
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +14 -2
- package/src/daemon/session-agent-loop-handlers.ts +9 -0
- package/src/daemon/session-agent-loop.ts +1 -0
- package/src/daemon/session-attachments.ts +5 -1
- package/src/daemon/session-error.ts +18 -0
- package/src/daemon/session-lifecycle.ts +4 -5
- package/src/daemon/session-media-retry.ts +15 -1
- package/src/daemon/session-surfaces.ts +0 -1
- package/src/daemon/session-tool-setup.ts +7 -4
- package/src/events/domain-events.ts +2 -1
- package/src/home-base/prebuilt/seed.ts +0 -1
- package/src/influencer/client.ts +7 -24
- package/src/media/gemini-image-service.ts +48 -3
- package/src/memory/app-store.ts +0 -4
- package/src/memory/conversation-attention-store.ts +3 -1
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +12 -0
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/messaging/providers/slack/client.ts +84 -26
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/adapters/slack.ts +90 -0
- package/src/notifications/destination-resolver.ts +42 -1
- package/src/notifications/emit-signal.ts +17 -1
- package/src/oauth/provider-profiles.ts +22 -0
- package/src/providers/anthropic/client.ts +3 -0
- package/src/providers/openai/client.ts +3 -0
- package/src/providers/retry.ts +9 -1
- package/src/runtime/actor-trust-resolver.ts +8 -0
- package/src/runtime/auth/require-bound-guardian.ts +44 -0
- package/src/runtime/auth/route-policy.ts +4 -8
- package/src/runtime/channel-approval-types.ts +18 -0
- package/src/runtime/channel-approvals.ts +8 -0
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +62 -3
- package/src/runtime/gateway-client.ts +36 -2
- package/src/runtime/gateway-internal-client.ts +86 -0
- package/src/runtime/guardian-action-service.ts +127 -0
- package/src/runtime/guardian-verification-templates.ts +16 -1
- package/src/runtime/http-server.ts +20 -49
- package/src/runtime/invite-redemption-service.ts +1 -1
- package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
- package/src/runtime/nl-approval-parser.ts +138 -0
- package/src/runtime/routes/approval-routes.ts +1 -40
- package/src/runtime/routes/channel-route-shared.ts +35 -1
- package/src/runtime/routes/contact-routes.ts +196 -28
- package/src/runtime/routes/guardian-action-routes.ts +19 -111
- package/src/runtime/routes/guardian-approval-interception.ts +76 -0
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +222 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
- package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
- package/src/runtime/slack-block-formatting.ts +176 -0
- package/src/schedule/scheduler.ts +11 -2
- package/src/tools/apps/executors.ts +16 -15
- package/src/tools/calls/call-end.ts +1 -1
- package/src/tools/computer-use/definitions.ts +16 -0
- package/src/tools/credentials/vault.ts +86 -2
- package/src/tools/network/script-proxy/session-manager.ts +28 -3
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/terminal/shell.ts +15 -5
- package/src/tools/tool-approval-handler.ts +48 -4
- package/src/tools/types.ts +38 -1
- package/src/util/errors.ts +5 -1
- package/src/util/retry.ts +21 -0
- package/src/watcher/providers/slack.ts +33 -3
- /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for managing per-channel permission profiles in Slack.
|
|
3
|
+
*
|
|
4
|
+
* Allows the assistant to configure which tools are available in specific
|
|
5
|
+
* Slack channels, set trust levels, and manage permission overrides.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type ChannelPermissionProfile,
|
|
10
|
+
getChannelPermissions,
|
|
11
|
+
removeChannelPermissionProfile,
|
|
12
|
+
setAllChannelPermissions,
|
|
13
|
+
setChannelPermissionProfile,
|
|
14
|
+
} from "../../../../config/channel-permission-profiles.js";
|
|
15
|
+
import type {
|
|
16
|
+
ToolContext,
|
|
17
|
+
ToolExecutionResult,
|
|
18
|
+
} from "../../../../tools/types.js";
|
|
19
|
+
import { err, ok } from "./shared.js";
|
|
20
|
+
|
|
21
|
+
export async function run(
|
|
22
|
+
input: Record<string, unknown>,
|
|
23
|
+
_context: ToolContext,
|
|
24
|
+
): Promise<ToolExecutionResult> {
|
|
25
|
+
const action = input.action as string;
|
|
26
|
+
|
|
27
|
+
if (!action) {
|
|
28
|
+
return err("action is required (list, get, set, remove, or clear).");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
switch (action) {
|
|
33
|
+
case "list": {
|
|
34
|
+
const perms = getChannelPermissions();
|
|
35
|
+
const entries = Object.entries(perms);
|
|
36
|
+
if (entries.length === 0) {
|
|
37
|
+
return ok(
|
|
38
|
+
'No channel permission profiles configured. Use action "set" to configure permissions for a channel.',
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return ok(JSON.stringify({ channelPermissions: perms }, null, 2));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
case "get": {
|
|
45
|
+
const channelId = input.channel_id as string | undefined;
|
|
46
|
+
if (!channelId) {
|
|
47
|
+
return err('channel_id is required for "get" action.');
|
|
48
|
+
}
|
|
49
|
+
const perms = getChannelPermissions();
|
|
50
|
+
const profile = perms[channelId];
|
|
51
|
+
if (!profile) {
|
|
52
|
+
return ok(
|
|
53
|
+
JSON.stringify(
|
|
54
|
+
{
|
|
55
|
+
channel_id: channelId,
|
|
56
|
+
profile: null,
|
|
57
|
+
message: "No permission profile configured for this channel.",
|
|
58
|
+
},
|
|
59
|
+
null,
|
|
60
|
+
2,
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return ok(JSON.stringify({ channel_id: channelId, profile }, null, 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case "set": {
|
|
68
|
+
const channelId = input.channel_id as string | undefined;
|
|
69
|
+
if (!channelId) {
|
|
70
|
+
return err('channel_id is required for "set" action.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const profile: ChannelPermissionProfile = {};
|
|
74
|
+
|
|
75
|
+
if (typeof input.label === "string") {
|
|
76
|
+
profile.label = input.label;
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(input.allowed_tool_categories)) {
|
|
79
|
+
profile.allowedToolCategories =
|
|
80
|
+
input.allowed_tool_categories as string[];
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(input.blocked_tools)) {
|
|
83
|
+
profile.blockedTools = input.blocked_tools as string[];
|
|
84
|
+
}
|
|
85
|
+
if (
|
|
86
|
+
input.trust_level === "restricted" ||
|
|
87
|
+
input.trust_level === "standard"
|
|
88
|
+
) {
|
|
89
|
+
profile.trustLevel = input.trust_level;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setChannelPermissionProfile(channelId, profile);
|
|
93
|
+
return ok(
|
|
94
|
+
JSON.stringify(
|
|
95
|
+
{
|
|
96
|
+
channel_id: channelId,
|
|
97
|
+
profile,
|
|
98
|
+
message: "Permission profile saved.",
|
|
99
|
+
},
|
|
100
|
+
null,
|
|
101
|
+
2,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
case "remove": {
|
|
107
|
+
const channelId = input.channel_id as string | undefined;
|
|
108
|
+
if (!channelId) {
|
|
109
|
+
return err('channel_id is required for "remove" action.');
|
|
110
|
+
}
|
|
111
|
+
const removed = removeChannelPermissionProfile(channelId);
|
|
112
|
+
return ok(
|
|
113
|
+
JSON.stringify(
|
|
114
|
+
{
|
|
115
|
+
channel_id: channelId,
|
|
116
|
+
removed,
|
|
117
|
+
message: removed
|
|
118
|
+
? "Permission profile removed."
|
|
119
|
+
: "No permission profile found for this channel.",
|
|
120
|
+
},
|
|
121
|
+
null,
|
|
122
|
+
2,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case "clear": {
|
|
128
|
+
setAllChannelPermissions({});
|
|
129
|
+
return ok(
|
|
130
|
+
JSON.stringify(
|
|
131
|
+
{ message: "All channel permission profiles cleared." },
|
|
132
|
+
null,
|
|
133
|
+
2,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
return err(
|
|
140
|
+
`Unknown action "${action}". Use list, get, set, remove, or clear.`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -140,6 +140,115 @@ function truncate(text: string, maxLen: number): string {
|
|
|
140
140
|
return text.slice(0, maxLen - 3) + "...";
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
|
+
type BlockKitBlock = Record<string, any>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Build Slack Block Kit blocks from digest results.
|
|
148
|
+
* Produces a structured Block Kit payload with header, per-channel sections,
|
|
149
|
+
* thread context blocks, and dividers.
|
|
150
|
+
*/
|
|
151
|
+
function buildBlockKitOutput(
|
|
152
|
+
digests: ChannelDigest[],
|
|
153
|
+
hoursBack: number,
|
|
154
|
+
totalAttempted: number,
|
|
155
|
+
skippedCount: number,
|
|
156
|
+
): BlockKitBlock[] {
|
|
157
|
+
const blocks: BlockKitBlock[] = [];
|
|
158
|
+
|
|
159
|
+
// Header block with scan summary
|
|
160
|
+
blocks.push({
|
|
161
|
+
type: "header",
|
|
162
|
+
text: {
|
|
163
|
+
type: "plain_text",
|
|
164
|
+
text: `Slack Digest — ${digests.length} channel${digests.length !== 1 ? "s" : ""} scanned`,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
blocks.push({
|
|
169
|
+
type: "section",
|
|
170
|
+
text: {
|
|
171
|
+
type: "mrkdwn",
|
|
172
|
+
text: `*Time range:* Last ${hoursBack} hour${hoursBack !== 1 ? "s" : ""} | *Channels attempted:* ${totalAttempted} | *Skipped:* ${skippedCount}`,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
blocks.push({ type: "divider" });
|
|
177
|
+
|
|
178
|
+
for (const digest of digests) {
|
|
179
|
+
if (digest.error) {
|
|
180
|
+
blocks.push({
|
|
181
|
+
type: "section",
|
|
182
|
+
text: {
|
|
183
|
+
type: "mrkdwn",
|
|
184
|
+
text: `${digest.isPrivate ? "\ud83d\udd12 " : ""}*#${digest.channelName}* — _Error: ${digest.error}_`,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
blocks.push({ type: "divider" });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Channel section with name, message count, privacy indicator
|
|
192
|
+
const privacyIcon = digest.isPrivate ? "\ud83d\udd12 " : "";
|
|
193
|
+
blocks.push({
|
|
194
|
+
type: "section",
|
|
195
|
+
text: {
|
|
196
|
+
type: "mrkdwn",
|
|
197
|
+
text: `${privacyIcon}*#${digest.channelName}* — ${digest.messageCount} message${digest.messageCount !== 1 ? "s" : ""}`,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Key participants as context
|
|
202
|
+
if (digest.keyParticipants.length > 0) {
|
|
203
|
+
blocks.push({
|
|
204
|
+
type: "context",
|
|
205
|
+
elements: [
|
|
206
|
+
{
|
|
207
|
+
type: "mrkdwn",
|
|
208
|
+
text: `*Active:* ${digest.keyParticipants.join(", ")}`,
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Thread previews as context blocks
|
|
215
|
+
for (const thread of digest.topThreads) {
|
|
216
|
+
const participantText =
|
|
217
|
+
thread.participants.length > 0
|
|
218
|
+
? thread.participants.join(", ")
|
|
219
|
+
: "unknown";
|
|
220
|
+
blocks.push({
|
|
221
|
+
type: "context",
|
|
222
|
+
elements: [
|
|
223
|
+
{
|
|
224
|
+
type: "mrkdwn",
|
|
225
|
+
text: `\ud83e\uddf5 *${thread.replyCount} replies* (${participantText}): ${thread.previewText}`,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
blocks.push({ type: "divider" });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Slack Block Kit enforces a 50-block maximum per message.
|
|
235
|
+
// Truncate and append a summary block when we exceed the limit.
|
|
236
|
+
const SLACK_BLOCK_LIMIT = 50;
|
|
237
|
+
if (blocks.length > SLACK_BLOCK_LIMIT) {
|
|
238
|
+
const overflow = blocks.length - (SLACK_BLOCK_LIMIT - 1);
|
|
239
|
+
blocks.length = SLACK_BLOCK_LIMIT - 1;
|
|
240
|
+
blocks.push({
|
|
241
|
+
type: "section",
|
|
242
|
+
text: {
|
|
243
|
+
type: "mrkdwn",
|
|
244
|
+
text: `_... and ${overflow} more block${overflow !== 1 ? "s" : ""} truncated (some channels omitted). Use \`channel_ids\` to drill into specific channels._`,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return blocks;
|
|
250
|
+
}
|
|
251
|
+
|
|
143
252
|
export async function run(
|
|
144
253
|
input: Record<string, unknown>,
|
|
145
254
|
_context: ToolContext,
|
|
@@ -148,6 +257,7 @@ export async function run(
|
|
|
148
257
|
const hoursBack = (input.hours_back as number) ?? 24;
|
|
149
258
|
const includeThreads = (input.include_threads as boolean) ?? true;
|
|
150
259
|
const maxChannels = (input.max_channels as number) ?? 20;
|
|
260
|
+
const format = (input.format as string) ?? "text";
|
|
151
261
|
|
|
152
262
|
try {
|
|
153
263
|
return await withSlackToken(async (token) => {
|
|
@@ -233,6 +343,16 @@ export async function run(
|
|
|
233
343
|
(r) => r.status === "rejected",
|
|
234
344
|
).length;
|
|
235
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
|
+
}
|
|
355
|
+
|
|
236
356
|
const result = {
|
|
237
357
|
scannedChannels: digests.length,
|
|
238
358
|
totalChannelsAttempted: channelsToScan.length,
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "Slack App Setup"
|
|
3
|
+
description: "Connect a Slack app to the Vellum Assistant via Socket Mode with guided app creation and guardian verification"
|
|
4
|
+
user-invocable: true
|
|
5
|
+
includes: ["guardian-verify-setup"]
|
|
6
|
+
metadata: { "vellum": { "emoji": "💬" } }
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are helping your user connect a Slack bot to the Vellum Assistant gateway via Socket Mode. The gateway manages the Socket Mode connection — it never hits the assistant runtime directly. When this skill is invoked, walk through each step below using only existing tools.
|
|
10
|
+
|
|
11
|
+
## Prerequisites — Check Before Starting
|
|
12
|
+
|
|
13
|
+
Before beginning setup, verify these conditions are met:
|
|
14
|
+
|
|
15
|
+
1. **Gateway API base URL is set and reachable:** Use the injected `INTERNAL_GATEWAY_BASE_URL`, then run `curl -sf "$INTERNAL_GATEWAY_BASE_URL/healthz"` — it should return gateway health JSON (for example `{"status":"ok"}`). If it fails, tell the user to start the assistant with `vellum wake` and wait for it to become healthy before continuing.
|
|
16
|
+
2. **Use gateway control-plane routes only.** Slack setup/config actions in this skill must call gateway endpoints under `/v1/integrations/slack/channel/*` — never call the assistant runtime port directly.
|
|
17
|
+
|
|
18
|
+
## Setup Steps
|
|
19
|
+
|
|
20
|
+
### Step 1: Generate Manifest & Create Slack App
|
|
21
|
+
|
|
22
|
+
Ask the user what they'd like to name their Slack bot and optionally provide a short description. Use their answers (or sensible defaults) to generate a pre-configured Slack app manifest.
|
|
23
|
+
|
|
24
|
+
Generate the manifest JSON:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"display_information": {
|
|
29
|
+
"name": "<user's chosen name>",
|
|
30
|
+
"description": "<user's chosen description>",
|
|
31
|
+
"background_color": "#1a1a2e"
|
|
32
|
+
},
|
|
33
|
+
"features": {
|
|
34
|
+
"app_home": {
|
|
35
|
+
"home_tab_enabled": true,
|
|
36
|
+
"messages_tab_enabled": true,
|
|
37
|
+
"messages_tab_read_only_enabled": false
|
|
38
|
+
},
|
|
39
|
+
"bot_user": {
|
|
40
|
+
"display_name": "<user's chosen name>",
|
|
41
|
+
"always_online": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"oauth_config": {
|
|
45
|
+
"scopes": {
|
|
46
|
+
"bot": [
|
|
47
|
+
"app_mentions:read",
|
|
48
|
+
"channels:history",
|
|
49
|
+
"chat:write",
|
|
50
|
+
"files:write",
|
|
51
|
+
"im:history",
|
|
52
|
+
"im:read",
|
|
53
|
+
"im:write",
|
|
54
|
+
"reactions:write",
|
|
55
|
+
"users:read"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"settings": {
|
|
60
|
+
"event_subscriptions": {
|
|
61
|
+
"bot_events": [
|
|
62
|
+
"app_home_opened",
|
|
63
|
+
"app_mention",
|
|
64
|
+
"message.channels",
|
|
65
|
+
"message.im"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"interactivity": { "is_enabled": true },
|
|
69
|
+
"org_deploy_enabled": false,
|
|
70
|
+
"socket_mode_enabled": true,
|
|
71
|
+
"token_rotation_enabled": false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
After generating the manifest, URL-encode it and construct the create-from-manifest link:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
https://api.slack.com/apps?new_app=1&manifest_json=<url_encoded_manifest>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Present the link to the user: "Click this link to create your Slack app. It's pre-configured with all the right permissions, events, and Socket Mode. Just select your workspace and click **Create**."
|
|
83
|
+
|
|
84
|
+
The manifest pre-configures everything needed: Socket Mode, 9 minimal bot scopes, 4 event subscriptions, bot user, App Home, and interactivity. No manual scope or event configuration is needed.
|
|
85
|
+
|
|
86
|
+
Wait for the user to confirm they've created the app before proceeding.
|
|
87
|
+
|
|
88
|
+
### Step 2: Generate App Token & Collect It
|
|
89
|
+
|
|
90
|
+
Tell the user to navigate to **Settings > Basic Information > App-Level Tokens** in their newly created Slack app, then:
|
|
91
|
+
|
|
92
|
+
1. Click **Generate Token and Scopes**
|
|
93
|
+
2. Token name: "Socket Mode" (or any name they prefer)
|
|
94
|
+
3. Add scope: `connections:write`
|
|
95
|
+
4. Click **Generate**
|
|
96
|
+
|
|
97
|
+
**Immediately** collect the app token securely:
|
|
98
|
+
|
|
99
|
+
- Call `credential_store` with `action: "prompt"`, `service: "slack_channel"`, `field: "app_token"`, `label: "App-Level Token"`, `placeholder: "xapp-..."`, `description: "Paste the App-Level Token you just generated"`
|
|
100
|
+
|
|
101
|
+
**IMPORTANT — Secure credential collection only:** Never accept tokens pasted in plaintext chat. If the user pastes a token in the conversation, inform them that for security reasons you cannot use tokens shared in chat and must collect it through the secure prompt instead.
|
|
102
|
+
|
|
103
|
+
### Step 3: Install App & Collect Bot Token
|
|
104
|
+
|
|
105
|
+
Tell the user to navigate to **Settings > Install App** in the sidebar, then click **Install to Workspace** and authorize the requested permissions (already pre-configured from the manifest).
|
|
106
|
+
|
|
107
|
+
After installation, collect the bot token securely:
|
|
108
|
+
|
|
109
|
+
- Call `credential_store` with `action: "prompt"`, `service: "slack_channel"`, `field: "bot_token"`, `label: "Bot User OAuth Token"`, `placeholder: "xoxb-..."`, `description: "Paste the Bot User OAuth Token shown after installing"`
|
|
110
|
+
|
|
111
|
+
**IMPORTANT — Secure credential collection only:** Never accept tokens pasted in plaintext chat. Always collect through the secure prompt.
|
|
112
|
+
|
|
113
|
+
### Step 4: Validate & Connect
|
|
114
|
+
|
|
115
|
+
After both tokens are collected, submit them to the gateway for validation and storage:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
curl -s -X POST "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/slack/channel/config" \
|
|
119
|
+
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
120
|
+
-H "Content-Type: application/json" \
|
|
121
|
+
-d '{"botToken": "<bot_token>", "appToken": "<app_token>"}'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The endpoint validates the bot token via Slack's `auth.test` API and stores both tokens in the secure key store.
|
|
125
|
+
|
|
126
|
+
**On success** (`success: true`, `connected: true`):
|
|
127
|
+
|
|
128
|
+
- Report the bot username and workspace name to the user
|
|
129
|
+
- Proceed to Step 5
|
|
130
|
+
|
|
131
|
+
**On failure:**
|
|
132
|
+
|
|
133
|
+
| Error | Action |
|
|
134
|
+
| ------------------------ | ------------------------------------------------------------------------------------ |
|
|
135
|
+
| `invalid_auth` | Bot token is invalid — re-prompt for the correct token (repeat Step 3 collection) |
|
|
136
|
+
| Missing scopes | Tell the user which scopes to add in the Slack app configuration |
|
|
137
|
+
| Socket Mode not enabled | Instruct the user to enable Socket Mode in the app settings |
|
|
138
|
+
| App token format invalid | Must start with `xapp-` — re-prompt for the correct token (repeat Step 2 collection) |
|
|
139
|
+
|
|
140
|
+
### Step 5: Guardian Verification
|
|
141
|
+
|
|
142
|
+
Tell the user: "Now let's verify your identity as the trusted guardian for Slack."
|
|
143
|
+
|
|
144
|
+
Load the **guardian-verify-setup** skill to handle the verification flow:
|
|
145
|
+
|
|
146
|
+
- Call `skill_load` with `skill: "guardian-verify-setup"` to load the dependency skill.
|
|
147
|
+
|
|
148
|
+
The guardian-verify-setup skill manages the full outbound verification flow for Slack, including:
|
|
149
|
+
|
|
150
|
+
- Collecting the user's Slack user ID as the destination
|
|
151
|
+
- Starting the outbound verification session via the gateway endpoint `POST /v1/integrations/guardian/outbound/start` with `channel: "slack"`
|
|
152
|
+
- Sending a verification code via Slack DM
|
|
153
|
+
- Auto-polling for completion (the guardian-verify-setup skill handles this)
|
|
154
|
+
- Checking guardian status to confirm the binding was created
|
|
155
|
+
|
|
156
|
+
Tell the user: _"I've loaded the guardian verification guide. It will walk you through linking your Slack account as the trusted guardian."_
|
|
157
|
+
|
|
158
|
+
After the guardian-verify-setup skill completes (or the user skips), continue to Step 6.
|
|
159
|
+
|
|
160
|
+
**Note:** Guardian verification is optional but recommended. If the user declines or wants to skip, proceed to Step 6 without blocking.
|
|
161
|
+
|
|
162
|
+
### Step 6: Verify Connection & Report Success
|
|
163
|
+
|
|
164
|
+
Check the final connection status:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
curl -s "$INTERNAL_GATEWAY_BASE_URL/v1/integrations/slack/channel/config" \
|
|
168
|
+
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Check guardian binding status:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
vellum integrations guardian status --channel slack --json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The Settings > Channels > Slack card auto-refreshes on view appear via `fetchSlackChannelConfig()`, so the user will see the "Connected" status badge when they open or re-open that page.
|
|
178
|
+
|
|
179
|
+
Summarize what was done:
|
|
180
|
+
|
|
181
|
+
- Bot connected: {username} in {workspace}
|
|
182
|
+
- Socket Mode: active
|
|
183
|
+
- Guardian: {verified | not configured}
|
|
184
|
+
- Usage: "@{botUsername} in any channel, or DM the bot directly"
|
|
185
|
+
|
|
186
|
+
Tell the user: "Your Slack bot is now connected! Open **Settings > Channels** to see it reflected there."
|
|
187
|
+
|
|
188
|
+
## Automated vs Manual Steps
|
|
189
|
+
|
|
190
|
+
| Step | Status | Details |
|
|
191
|
+
| ----------------------- | ---------------------------- | ------------------------------------------------------------- |
|
|
192
|
+
| App manifest generation | Automated | Skill generates manifest JSON with all scopes/events/settings |
|
|
193
|
+
| App creation | Manual (one-click) | User clicks manifest URL, selects workspace, creates |
|
|
194
|
+
| App token generation | Manual | User generates in Slack settings (can't be automated) |
|
|
195
|
+
| App installation | Manual | User clicks "Install to Workspace" |
|
|
196
|
+
| Token collection | Manual (secure prompt) | Via `credential_store` — never plaintext |
|
|
197
|
+
| Token validation | Automated | `auth.test` validates bot token on submission |
|
|
198
|
+
| Socket Mode connection | Automated | Gateway connects when tokens are stored |
|
|
199
|
+
| Routing | Automated (single-assistant) | CLI sets defaults |
|
|
200
|
+
| Guardian verification | Semi-automated | Via `guardian-verify-setup` skill |
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"objective": {
|
|
17
17
|
"type": "string",
|
|
18
|
-
"description": "The task objective
|
|
18
|
+
"description": "The task objective \u2014 what the subagent should accomplish"
|
|
19
19
|
},
|
|
20
20
|
"context": {
|
|
21
21
|
"type": "string",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"send_result_to_user": {
|
|
25
25
|
"type": "boolean",
|
|
26
26
|
"description": "Whether to present the subagent's result to the user when it completes. Defaults to true. Set to false for internal/silent processing."
|
|
27
|
+
},
|
|
28
|
+
"reason": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"required": ["label", "objective"]
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
},
|
|
34
38
|
{
|
|
35
39
|
"name": "subagent_status",
|
|
36
|
-
"description": "Get the status of a specific subagent or list all subagents for the current session. Only use this when the user explicitly asks about subagent status
|
|
40
|
+
"description": "Get the status of a specific subagent or list all subagents for the current session. Only use this when the user explicitly asks about subagent status \u2014 do NOT poll automatically, as you will be notified when subagents complete.",
|
|
37
41
|
"category": "orchestration",
|
|
38
42
|
"risk": "low",
|
|
39
43
|
"input_schema": {
|
|
@@ -42,6 +46,10 @@
|
|
|
42
46
|
"subagent_id": {
|
|
43
47
|
"type": "string",
|
|
44
48
|
"description": "Optional subagent ID to query. If omitted, returns all subagents for this session."
|
|
49
|
+
},
|
|
50
|
+
"reason": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
45
53
|
}
|
|
46
54
|
},
|
|
47
55
|
"required": []
|
|
@@ -60,6 +68,10 @@
|
|
|
60
68
|
"subagent_id": {
|
|
61
69
|
"type": "string",
|
|
62
70
|
"description": "The ID of the subagent to abort."
|
|
71
|
+
},
|
|
72
|
+
"reason": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
63
75
|
}
|
|
64
76
|
},
|
|
65
77
|
"required": ["subagent_id"]
|
|
@@ -82,6 +94,10 @@
|
|
|
82
94
|
"content": {
|
|
83
95
|
"type": "string",
|
|
84
96
|
"description": "The message content to send to the subagent."
|
|
97
|
+
},
|
|
98
|
+
"reason": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
85
101
|
}
|
|
86
102
|
},
|
|
87
103
|
"required": ["subagent_id", "content"]
|
|
@@ -100,6 +116,10 @@
|
|
|
100
116
|
"subagent_id": {
|
|
101
117
|
"type": "string",
|
|
102
118
|
"description": "The ID of the subagent whose output to read."
|
|
119
|
+
},
|
|
120
|
+
"reason": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "Brief non-technical explanation of why this tool is being called"
|
|
103
123
|
}
|
|
104
124
|
},
|
|
105
125
|
"required": ["subagent_id"]
|