@vellumai/assistant 0.11.1 → 0.11.2-staging.2
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/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -1,114 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack Web API
|
|
2
|
+
* Slack Web API feature surface for streaming replies, uploads, and channel
|
|
3
|
+
* metadata.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Transport (retries, envelope checking, the unified `SlackApiError`) lives in
|
|
6
|
+
* `web-api-transport.ts`; identity selection lives in `auth.ts`
|
|
7
|
+
* (`resolveSlackAuth`). Each call here names the identity it acts as, per the
|
|
8
|
+
* rules in `auth.ts`:
|
|
9
|
+
* - "bot" for everything the assistant does as itself: streaming replies,
|
|
10
|
+
* uploads, reading its own message blocks for edit-in-place.
|
|
11
|
+
* - "user" only as a bounded FALLBACK: `conversations.info` channel-name
|
|
12
|
+
* resolution acts as the bot first and retries with the stored user token
|
|
13
|
+
* only when the bot cannot see the channel. See
|
|
14
|
+
* {@link getSlackConversationInfo} for why that stays within the
|
|
15
|
+
* neutral-bot rule for route-reachable calls.
|
|
7
16
|
*/
|
|
8
17
|
|
|
9
18
|
import type { KnownBlock } from "@slack/types";
|
|
10
19
|
import type { SlackStreamTask } from "@vellumai/gateway-client";
|
|
11
20
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export type SlackErrorCategory =
|
|
27
|
-
| "auth"
|
|
28
|
-
| "rate_limit"
|
|
29
|
-
| "not_found"
|
|
30
|
-
| "permission"
|
|
31
|
-
| "channel_not_found"
|
|
32
|
-
| "client_error"
|
|
33
|
-
| "transient"
|
|
34
|
-
| "unknown";
|
|
35
|
-
|
|
36
|
-
const SLACK_ERROR_CODE_MAP: Record<string, SlackErrorCategory> = {
|
|
37
|
-
invalid_auth: "auth",
|
|
38
|
-
token_expired: "auth",
|
|
39
|
-
token_revoked: "auth",
|
|
40
|
-
not_authed: "auth",
|
|
41
|
-
account_inactive: "auth",
|
|
42
|
-
org_login_required: "auth",
|
|
43
|
-
rate_limited: "rate_limit",
|
|
44
|
-
ratelimited: "rate_limit",
|
|
45
|
-
channel_not_found: "channel_not_found",
|
|
46
|
-
is_archived: "channel_not_found",
|
|
47
|
-
not_in_channel: "permission",
|
|
48
|
-
missing_scope: "permission",
|
|
49
|
-
ekm_access_denied: "permission",
|
|
50
|
-
not_allowed_token_type: "permission",
|
|
51
|
-
restricted_action: "permission",
|
|
52
|
-
cannot_dm_bot: "permission",
|
|
53
|
-
user_not_found: "not_found",
|
|
54
|
-
message_not_found: "not_found",
|
|
55
|
-
thread_not_found: "not_found",
|
|
56
|
-
invalid_blocks: "client_error",
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
function classifySlackError(errorCode: string | undefined): SlackErrorCategory {
|
|
60
|
-
if (!errorCode) {
|
|
61
|
-
return "unknown";
|
|
62
|
-
}
|
|
63
|
-
return SLACK_ERROR_CODE_MAP[errorCode] ?? "unknown";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export class SlackApiError extends Error {
|
|
67
|
-
readonly slackError: string | undefined;
|
|
68
|
-
readonly category: SlackErrorCategory;
|
|
69
|
-
|
|
70
|
-
constructor(slackError: string | undefined) {
|
|
71
|
-
super(`Slack API error: ${slackError ?? "unknown"}`);
|
|
72
|
-
this.name = "SlackApiError";
|
|
73
|
-
this.slackError = slackError;
|
|
74
|
-
this.category = classifySlackError(slackError);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// Credential resolution
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
async function resolveBotToken(): Promise<string> {
|
|
83
|
-
const botToken = await getSecureKeyAsync(
|
|
84
|
-
credentialKey("slack_channel", "bot_token"),
|
|
85
|
-
);
|
|
86
|
-
if (!botToken) {
|
|
87
|
-
throw new Error("Slack bot token not configured");
|
|
88
|
-
}
|
|
89
|
-
return botToken;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// Core API caller with rate-limit retries
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
interface SlackApiResponse {
|
|
97
|
-
ok: boolean;
|
|
98
|
-
error?: string;
|
|
21
|
+
import { resolveSlackAuth, type SlackAuthIdentity } from "./auth.js";
|
|
22
|
+
import { conversationInfo } from "./client.js";
|
|
23
|
+
import type {
|
|
24
|
+
SlackApiResponse,
|
|
25
|
+
SlackConversationInfoResponse,
|
|
26
|
+
} from "./types.js";
|
|
27
|
+
import {
|
|
28
|
+
SlackApiError,
|
|
29
|
+
slackRequest,
|
|
30
|
+
type SlackRequestOptions,
|
|
31
|
+
} from "./web-api-transport.js";
|
|
32
|
+
|
|
33
|
+
/** Envelope fields the outbound surfaces read off successful responses. */
|
|
34
|
+
interface SlackOutboundApiResponse extends SlackApiResponse {
|
|
99
35
|
ts?: string;
|
|
100
36
|
upload_url?: string;
|
|
101
37
|
file_id?: string;
|
|
102
38
|
}
|
|
103
39
|
|
|
104
|
-
interface SlackConversationsInfoResponse extends SlackApiResponse {
|
|
105
|
-
channel?: {
|
|
106
|
-
id?: unknown;
|
|
107
|
-
name?: unknown;
|
|
108
|
-
name_normalized?: unknown;
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
40
|
export interface SlackConversationInfo {
|
|
113
41
|
id: string;
|
|
114
42
|
name?: string;
|
|
@@ -116,160 +44,45 @@ export interface SlackConversationInfo {
|
|
|
116
44
|
}
|
|
117
45
|
|
|
118
46
|
/**
|
|
119
|
-
*
|
|
47
|
+
* Resolve the named identity and dispatch through the shared transport.
|
|
48
|
+
* Throws when no Slack credentials are configured at all; `resolveSlackAuth`
|
|
49
|
+
* itself falls back user -> bot -> legacy OAuth connection before that.
|
|
50
|
+
*/
|
|
51
|
+
async function slackApiRequest<T extends SlackApiResponse>(
|
|
52
|
+
identity: SlackAuthIdentity,
|
|
53
|
+
method: string,
|
|
54
|
+
opts: SlackRequestOptions,
|
|
55
|
+
): Promise<T> {
|
|
56
|
+
const auth = await resolveSlackAuth(identity);
|
|
57
|
+
if (!auth) {
|
|
58
|
+
throw new Error("Slack bot token not configured");
|
|
59
|
+
}
|
|
60
|
+
return slackRequest<T>(auth, method, opts);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Call a Slack Web API write method as the bot, with rate-limit retries.
|
|
120
65
|
*
|
|
121
|
-
* Throws SlackApiError for non-retryable Slack-level errors
|
|
122
|
-
*
|
|
66
|
+
* Throws SlackApiError for non-retryable Slack-level errors and for
|
|
67
|
+
* transport-level failures after exhausting retries.
|
|
123
68
|
*/
|
|
124
69
|
export async function callSlackApi(
|
|
125
70
|
method: string,
|
|
126
71
|
body: Record<string, unknown>,
|
|
127
|
-
): Promise<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
let lastError: string | undefined;
|
|
131
|
-
|
|
132
|
-
for (let attempt = 0; attempt <= SLACK_MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
133
|
-
const response = await fetch(`${SLACK_API_BASE}/${method}`, {
|
|
134
|
-
method: "POST",
|
|
135
|
-
headers: {
|
|
136
|
-
Authorization: `Bearer ${botToken}`,
|
|
137
|
-
"Content-Type": "application/json",
|
|
138
|
-
},
|
|
139
|
-
body: JSON.stringify(body),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
if (response.status === 429) {
|
|
143
|
-
if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
|
|
144
|
-
throw new Error("Slack rate limit exceeded after retries");
|
|
145
|
-
}
|
|
146
|
-
const retryAfter =
|
|
147
|
-
parseInt(response.headers.get("Retry-After") ?? "", 10) ||
|
|
148
|
-
SLACK_DEFAULT_RETRY_AFTER_S;
|
|
149
|
-
log.warn({ method, retryAfter, attempt }, "Slack rate limited, retrying");
|
|
150
|
-
await new Promise((r) => setTimeout(r, retryAfter * 1000));
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (response.status >= 500) {
|
|
155
|
-
if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
`Slack ${method} failed with status ${response.status} after retries`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
log.warn(
|
|
161
|
-
{ method, status: response.status, attempt },
|
|
162
|
-
"Slack 5xx error, retrying",
|
|
163
|
-
);
|
|
164
|
-
await new Promise((r) =>
|
|
165
|
-
setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
|
|
166
|
-
);
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const data = (await response.json()) as SlackApiResponse;
|
|
171
|
-
|
|
172
|
-
if (!data.ok) {
|
|
173
|
-
lastError = data.error;
|
|
174
|
-
const category = classifySlackError(data.error);
|
|
175
|
-
|
|
176
|
-
if (category === "rate_limit" && attempt < SLACK_MAX_RATE_LIMIT_RETRIES) {
|
|
177
|
-
log.warn(
|
|
178
|
-
{ method, slackError: data.error, attempt },
|
|
179
|
-
"Slack rate limited (body), retrying",
|
|
180
|
-
);
|
|
181
|
-
await new Promise((r) =>
|
|
182
|
-
setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
|
|
183
|
-
);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
throw new SlackApiError(data.error);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return data;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
throw new Error(
|
|
194
|
-
`Slack ${method} failed after retries: ${lastError ?? "unknown"}`,
|
|
195
|
-
);
|
|
72
|
+
): Promise<SlackOutboundApiResponse> {
|
|
73
|
+
return slackApiRequest<SlackOutboundApiResponse>("bot", method, { body });
|
|
196
74
|
}
|
|
197
75
|
|
|
198
76
|
/**
|
|
199
|
-
* Call a Slack Web API
|
|
77
|
+
* Call a Slack Web API method as the bot with a form-urlencoded body.
|
|
200
78
|
*/
|
|
201
|
-
async function
|
|
79
|
+
export async function callSlackApiForm(
|
|
202
80
|
method: string,
|
|
203
81
|
params: URLSearchParams,
|
|
204
|
-
): Promise<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let lastError: string | undefined;
|
|
210
|
-
|
|
211
|
-
for (let attempt = 0; attempt <= SLACK_MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
212
|
-
const response = await fetch(url, {
|
|
213
|
-
method: "GET",
|
|
214
|
-
headers: {
|
|
215
|
-
Authorization: `Bearer ${botToken}`,
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
if (response.status === 429) {
|
|
220
|
-
if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
|
|
221
|
-
throw new Error("Slack rate limit exceeded after retries");
|
|
222
|
-
}
|
|
223
|
-
const retryAfter =
|
|
224
|
-
parseInt(response.headers.get("Retry-After") ?? "", 10) ||
|
|
225
|
-
SLACK_DEFAULT_RETRY_AFTER_S;
|
|
226
|
-
log.warn({ method, retryAfter, attempt }, "Slack rate limited, retrying");
|
|
227
|
-
await new Promise((r) => setTimeout(r, retryAfter * 1000));
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (response.status >= 500) {
|
|
232
|
-
if (attempt >= SLACK_MAX_RATE_LIMIT_RETRIES) {
|
|
233
|
-
throw new Error(
|
|
234
|
-
`Slack ${method} failed with status ${response.status} after retries`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
log.warn(
|
|
238
|
-
{ method, status: response.status, attempt },
|
|
239
|
-
"Slack 5xx error, retrying",
|
|
240
|
-
);
|
|
241
|
-
await new Promise((r) =>
|
|
242
|
-
setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
|
|
243
|
-
);
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const data = (await response.json()) as SlackApiResponse;
|
|
248
|
-
|
|
249
|
-
if (!data.ok) {
|
|
250
|
-
lastError = data.error;
|
|
251
|
-
const category = classifySlackError(data.error);
|
|
252
|
-
|
|
253
|
-
if (category === "rate_limit" && attempt < SLACK_MAX_RATE_LIMIT_RETRIES) {
|
|
254
|
-
log.warn(
|
|
255
|
-
{ method, slackError: data.error, attempt },
|
|
256
|
-
"Slack rate limited (body), retrying",
|
|
257
|
-
);
|
|
258
|
-
await new Promise((r) =>
|
|
259
|
-
setTimeout(r, SLACK_DEFAULT_RETRY_AFTER_S * 1000),
|
|
260
|
-
);
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
throw new SlackApiError(data.error);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return data;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
throw new Error(
|
|
271
|
-
`Slack ${method} failed after retries: ${lastError ?? "unknown"}`,
|
|
272
|
-
);
|
|
82
|
+
): Promise<SlackOutboundApiResponse> {
|
|
83
|
+
return slackApiRequest<SlackOutboundApiResponse>("bot", method, {
|
|
84
|
+
form: params,
|
|
85
|
+
});
|
|
273
86
|
}
|
|
274
87
|
|
|
275
88
|
// ---------------------------------------------------------------------------
|
|
@@ -428,14 +241,9 @@ function normalizeSlackString(value: unknown): string | undefined {
|
|
|
428
241
|
return trimmed || undefined;
|
|
429
242
|
}
|
|
430
243
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
):
|
|
434
|
-
const data = (await callSlackApiGet(
|
|
435
|
-
"conversations.info",
|
|
436
|
-
new URLSearchParams({ channel: channelId }),
|
|
437
|
-
)) as SlackConversationsInfoResponse;
|
|
438
|
-
|
|
244
|
+
function parseSlackConversationInfo(
|
|
245
|
+
data: SlackConversationInfoResponse,
|
|
246
|
+
): SlackConversationInfo | null {
|
|
439
247
|
const id = normalizeSlackString(data.channel?.id);
|
|
440
248
|
if (!id) {
|
|
441
249
|
return null;
|
|
@@ -451,6 +259,52 @@ export async function getSlackConversationInfo(
|
|
|
451
259
|
};
|
|
452
260
|
}
|
|
453
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Resolve a channel's identity and display names via `conversations.info`.
|
|
264
|
+
*
|
|
265
|
+
* Acts as the bot first, honoring the neutral-bot rule for route-reachable
|
|
266
|
+
* calls (`auth.ts`): this function backs the `slack_channel_name_resolve`
|
|
267
|
+
* route. When the bot cannot see the channel (`channel_not_found` /
|
|
268
|
+
* permission errors) and a DISTINCT user token is stored, it retries once as
|
|
269
|
+
* the owner, so channels the owner is in but the bot is not still resolve.
|
|
270
|
+
* The wider identity is used only for the exact case the bot cannot answer,
|
|
271
|
+
* and the response is scoped to a channel the calling conversation is
|
|
272
|
+
* already bound to. Legacy OAuth installs have no separate user identity, so
|
|
273
|
+
* the fallback never fires there.
|
|
274
|
+
*/
|
|
275
|
+
export async function getSlackConversationInfo(
|
|
276
|
+
channelId: string,
|
|
277
|
+
): Promise<SlackConversationInfo | null> {
|
|
278
|
+
const botAuth = await resolveSlackAuth("bot");
|
|
279
|
+
if (!botAuth) {
|
|
280
|
+
throw new Error("Slack bot token not configured");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
return parseSlackConversationInfo(
|
|
285
|
+
await conversationInfo(botAuth, channelId),
|
|
286
|
+
);
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (
|
|
289
|
+
!(err instanceof SlackApiError) ||
|
|
290
|
+
(err.category !== "channel_not_found" && err.category !== "permission")
|
|
291
|
+
) {
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
const userAuth = await resolveSlackAuth("user");
|
|
295
|
+
if (
|
|
296
|
+
typeof botAuth !== "string" ||
|
|
297
|
+
typeof userAuth !== "string" ||
|
|
298
|
+
userAuth === botAuth
|
|
299
|
+
) {
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
return parseSlackConversationInfo(
|
|
303
|
+
await conversationInfo(userAuth, channelId),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
454
308
|
interface SlackHistoryResponse extends SlackApiResponse {
|
|
455
309
|
messages?: Array<{ ts?: string; blocks?: unknown[] }>;
|
|
456
310
|
}
|
|
@@ -458,55 +312,34 @@ interface SlackHistoryResponse extends SlackApiResponse {
|
|
|
458
312
|
/**
|
|
459
313
|
* Fetch the Block Kit blocks of a single channel message by timestamp.
|
|
460
314
|
*
|
|
461
|
-
* Used to edit a message in place while preserving its existing content
|
|
315
|
+
* Used to edit a message in place while preserving its existing content, e.g.
|
|
462
316
|
* withdrawing an approval card's buttons without discarding the card body.
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
317
|
+
* Acts as the bot: the messages being edited are the bot's own. Returns null
|
|
318
|
+
* when the message can't be read (missing `*:history` scope, a threaded reply
|
|
319
|
+
* not present in channel history, or a deleted message) so callers can degrade
|
|
320
|
+
* gracefully instead of failing the edit.
|
|
466
321
|
*/
|
|
467
322
|
export async function getSlackMessageBlocks(
|
|
468
323
|
channelId: string,
|
|
469
324
|
ts: string,
|
|
470
325
|
): Promise<unknown[] | null> {
|
|
471
|
-
const data =
|
|
326
|
+
const data = await slackApiRequest<SlackHistoryResponse>(
|
|
327
|
+
"bot",
|
|
472
328
|
"conversations.history",
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
329
|
+
{
|
|
330
|
+
query: {
|
|
331
|
+
channel: channelId,
|
|
332
|
+
latest: ts,
|
|
333
|
+
oldest: ts,
|
|
334
|
+
inclusive: "true",
|
|
335
|
+
limit: "1",
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
);
|
|
481
339
|
const message = data.messages?.find((m) => m.ts === ts) ?? data.messages?.[0];
|
|
482
340
|
return Array.isArray(message?.blocks) ? message.blocks : null;
|
|
483
341
|
}
|
|
484
342
|
|
|
485
|
-
/**
|
|
486
|
-
* Call a Slack Web API method with form-urlencoded body.
|
|
487
|
-
*/
|
|
488
|
-
export async function callSlackApiForm(
|
|
489
|
-
method: string,
|
|
490
|
-
params: URLSearchParams,
|
|
491
|
-
): Promise<SlackApiResponse> {
|
|
492
|
-
const botToken = await resolveBotToken();
|
|
493
|
-
|
|
494
|
-
const response = await fetch(`${SLACK_API_BASE}/${method}`, {
|
|
495
|
-
method: "POST",
|
|
496
|
-
headers: {
|
|
497
|
-
Authorization: `Bearer ${botToken}`,
|
|
498
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
499
|
-
},
|
|
500
|
-
body: params,
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
const data = (await response.json()) as SlackApiResponse;
|
|
504
|
-
if (!data.ok) {
|
|
505
|
-
throw new SlackApiError(data.error);
|
|
506
|
-
}
|
|
507
|
-
return data;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
343
|
/**
|
|
511
344
|
* Upload raw bytes to a Slack-provided upload URL.
|
|
512
345
|
*/
|