@vellumai/assistant 0.8.3 → 0.8.4
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/docker-entrypoint.sh +0 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +610 -16
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +25 -7
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-resolver.test.ts +85 -1
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/openai-provider.test.ts +105 -0
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +51 -28
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +57 -20
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +19 -0
- package/src/config/feature-flag-registry.json +99 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-v2.ts +48 -1
- package/src/context/compactor.ts +8 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
- package/src/daemon/conversation-agent-loop.ts +223 -54
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +135 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -5
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +79 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/db-init.ts +16 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -2
- package/src/memory/memory-retrospective-job.ts +408 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +25 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +31 -14
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +21 -10
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +9 -1
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +82 -9
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -68
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +8 -0
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +53 -3
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +15 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/model-catalog.ts +48 -1
- package/src/providers/openai/chat-completions-provider.ts +57 -20
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +151 -56
- package/src/runtime/auth/route-policy.ts +7 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -4
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +60 -1
- package/src/runtime/routes/conversation-routes.ts +281 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +12 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +4 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -15,10 +15,16 @@ import {
|
|
|
15
15
|
isChannelId,
|
|
16
16
|
parseInterfaceId,
|
|
17
17
|
} from "../../channels/types.js";
|
|
18
|
+
import { getConfig } from "../../config/loader.js";
|
|
18
19
|
import {
|
|
19
20
|
createApprovalConversationGenerator,
|
|
20
21
|
createApprovalCopyGenerator,
|
|
21
22
|
} from "../../daemon/approval-generators.js";
|
|
23
|
+
import { findConversation } from "../../daemon/conversation-store.js";
|
|
24
|
+
import {
|
|
25
|
+
canonicalizeTimeZone,
|
|
26
|
+
resolveTurnTimezoneContext,
|
|
27
|
+
} from "../../daemon/date-context.js";
|
|
22
28
|
import { getDiskPressureStatus } from "../../daemon/disk-pressure-guard.js";
|
|
23
29
|
import { classifyDiskPressureTurnPolicy } from "../../daemon/disk-pressure-policy.js";
|
|
24
30
|
import { processMessage } from "../../daemon/process-message.js";
|
|
@@ -55,7 +61,10 @@ import {
|
|
|
55
61
|
import { markProcessed } from "../../memory/delivery-status.js";
|
|
56
62
|
import { upsertBinding } from "../../memory/external-conversation-store.js";
|
|
57
63
|
import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
|
|
58
|
-
import {
|
|
64
|
+
import {
|
|
65
|
+
resolveSlackBotUserId,
|
|
66
|
+
withSlackBotToken,
|
|
67
|
+
} from "../../messaging/providers/slack/adapter.js";
|
|
59
68
|
import {
|
|
60
69
|
backfillDm,
|
|
61
70
|
backfillThreadWindowPage,
|
|
@@ -63,6 +72,8 @@ import {
|
|
|
63
72
|
} from "../../messaging/providers/slack/backfill.js";
|
|
64
73
|
import { downloadSlackFile } from "../../messaging/providers/slack/download.js";
|
|
65
74
|
import {
|
|
75
|
+
buildSlackTimezoneMetadata,
|
|
76
|
+
formatSlackTimezoneLabel,
|
|
66
77
|
mergeSlackMetadata,
|
|
67
78
|
readSlackMetadataFromMessageMetadata,
|
|
68
79
|
type SlackFileMetadata,
|
|
@@ -102,6 +113,108 @@ const DISK_PRESSURE_REMOTE_BLOCK_REPLY =
|
|
|
102
113
|
let deleteLookupRetries = 5;
|
|
103
114
|
let deleteLookupDelayMs = 2000;
|
|
104
115
|
|
|
116
|
+
interface SlackActorTimezoneMetadata {
|
|
117
|
+
timezone?: string;
|
|
118
|
+
timezoneLabel?: string;
|
|
119
|
+
timezoneOffsetSeconds?: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function trimMetadataString(
|
|
123
|
+
metadata: Record<string, unknown> | undefined,
|
|
124
|
+
key: string,
|
|
125
|
+
): string | undefined {
|
|
126
|
+
const value = metadata?.[key];
|
|
127
|
+
if (typeof value !== "string") return undefined;
|
|
128
|
+
const trimmed = value.trim();
|
|
129
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseSlackActorTimezoneMetadata(
|
|
133
|
+
sourceChannel: string,
|
|
134
|
+
metadata: Record<string, unknown> | undefined,
|
|
135
|
+
): SlackActorTimezoneMetadata | undefined {
|
|
136
|
+
if (sourceChannel !== "slack") return undefined;
|
|
137
|
+
|
|
138
|
+
const timezone = trimMetadataString(metadata, "timezone");
|
|
139
|
+
const timezoneLabel = trimMetadataString(metadata, "timezoneLabel");
|
|
140
|
+
const rawOffset = metadata?.timezoneOffsetSeconds;
|
|
141
|
+
const timezoneOffsetSeconds =
|
|
142
|
+
typeof rawOffset === "number" && Number.isFinite(rawOffset)
|
|
143
|
+
? rawOffset
|
|
144
|
+
: undefined;
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
timezone === undefined &&
|
|
148
|
+
timezoneLabel === undefined &&
|
|
149
|
+
timezoneOffsetSeconds === undefined
|
|
150
|
+
) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
...(timezone ? { timezone } : {}),
|
|
156
|
+
...(timezoneLabel ? { timezoneLabel } : {}),
|
|
157
|
+
...(timezoneOffsetSeconds !== undefined ? { timezoneOffsetSeconds } : {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function attachSlackRequesterTimezone(
|
|
162
|
+
trustCtx: TrustContext,
|
|
163
|
+
timezone: SlackActorTimezoneMetadata | undefined,
|
|
164
|
+
): TrustContext {
|
|
165
|
+
if (!timezone) return trustCtx;
|
|
166
|
+
return {
|
|
167
|
+
...trustCtx,
|
|
168
|
+
...(timezone.timezone ? { requesterTimezone: timezone.timezone } : {}),
|
|
169
|
+
...(timezone.timezoneLabel
|
|
170
|
+
? { requesterTimezoneLabel: timezone.timezoneLabel }
|
|
171
|
+
: {}),
|
|
172
|
+
...(timezone.timezoneOffsetSeconds !== undefined
|
|
173
|
+
? { requesterTimezoneOffsetSeconds: timezone.timezoneOffsetSeconds }
|
|
174
|
+
: {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function resolveSlackTranscriptTimestampTimezone(
|
|
179
|
+
clientTimezone?: string | null,
|
|
180
|
+
): {
|
|
181
|
+
timestampTimezone: string;
|
|
182
|
+
timestampTimezoneLabel?: string;
|
|
183
|
+
} {
|
|
184
|
+
const config = getConfig();
|
|
185
|
+
const timestampTimezone = resolveTurnTimezoneContext({
|
|
186
|
+
configuredUserTimeZone: config.ui?.userTimezone ?? null,
|
|
187
|
+
clientTimezone: clientTimezone ?? null,
|
|
188
|
+
detectedTimezone: config.ui?.detectedTimezone ?? null,
|
|
189
|
+
hostTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
190
|
+
}).effectiveTimezone;
|
|
191
|
+
const timestampTimezoneLabel = formatSlackTimezoneLabel(timestampTimezone);
|
|
192
|
+
return {
|
|
193
|
+
timestampTimezone,
|
|
194
|
+
...(timestampTimezoneLabel ? { timestampTimezoneLabel } : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function resolveInboundClientTimezone(params: {
|
|
199
|
+
bodyClientTimezone?: unknown;
|
|
200
|
+
sourceMetadata?: Record<string, unknown>;
|
|
201
|
+
conversationId: string;
|
|
202
|
+
}): string | undefined {
|
|
203
|
+
const bodyClientTimezone =
|
|
204
|
+
typeof params.bodyClientTimezone === "string"
|
|
205
|
+
? canonicalizeTimeZone(params.bodyClientTimezone)
|
|
206
|
+
: undefined;
|
|
207
|
+
const metadataClientTimezone =
|
|
208
|
+
typeof params.sourceMetadata?.clientTimezone === "string"
|
|
209
|
+
? canonicalizeTimeZone(params.sourceMetadata.clientTimezone)
|
|
210
|
+
: undefined;
|
|
211
|
+
return (
|
|
212
|
+
bodyClientTimezone ??
|
|
213
|
+
metadataClientTimezone ??
|
|
214
|
+
findConversation(params.conversationId)?.clientTimezone
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
105
218
|
/**
|
|
106
219
|
* Test-only override for the delete-lookup retry timings. Used by
|
|
107
220
|
* tests that exercise the "no such message" path without waiting
|
|
@@ -143,6 +256,7 @@ export async function handleChannelInbound({
|
|
|
143
256
|
replyCallbackUrl?: string;
|
|
144
257
|
callbackQueryId?: string;
|
|
145
258
|
callbackData?: string;
|
|
259
|
+
clientTimezone?: unknown;
|
|
146
260
|
};
|
|
147
261
|
|
|
148
262
|
const {
|
|
@@ -168,6 +282,14 @@ export async function handleChannelInbound({
|
|
|
168
282
|
}
|
|
169
283
|
|
|
170
284
|
const sourceChannel = body.sourceChannel;
|
|
285
|
+
const slackChannelName =
|
|
286
|
+
sourceChannel === "slack" && typeof sourceMetadata?.channelName === "string"
|
|
287
|
+
? sourceMetadata.channelName.trim() || null
|
|
288
|
+
: null;
|
|
289
|
+
const slackActorTimezone = parseSlackActorTimezoneMetadata(
|
|
290
|
+
sourceChannel,
|
|
291
|
+
sourceMetadata,
|
|
292
|
+
);
|
|
171
293
|
|
|
172
294
|
if (!body.interface || typeof body.interface !== "string") {
|
|
173
295
|
throw new BadRequestError("interface is required");
|
|
@@ -547,6 +669,7 @@ export async function handleChannelInbound({
|
|
|
547
669
|
conversationId: result.conversationId,
|
|
548
670
|
sourceChannel,
|
|
549
671
|
externalChatId: conversationExternalId,
|
|
672
|
+
externalChatName: slackChannelName,
|
|
550
673
|
externalThreadId: slackThreadTs ?? null,
|
|
551
674
|
externalUserId: canonicalSenderId ?? rawSenderId ?? null,
|
|
552
675
|
displayName: body.actorDisplayName ?? null,
|
|
@@ -557,14 +680,17 @@ export async function handleChannelInbound({
|
|
|
557
680
|
// ── Actor role resolution ──
|
|
558
681
|
// Uses shared channel-agnostic resolution so all ingress paths classify
|
|
559
682
|
// guardian vs non-guardian actors the same way.
|
|
560
|
-
const trustCtx: TrustContext =
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
683
|
+
const trustCtx: TrustContext = attachSlackRequesterTimezone(
|
|
684
|
+
resolveTrustContext({
|
|
685
|
+
assistantId: canonicalAssistantId,
|
|
686
|
+
sourceChannel,
|
|
687
|
+
conversationExternalId,
|
|
688
|
+
actorExternalId: rawSenderId,
|
|
689
|
+
actorUsername: body.actorUsername,
|
|
690
|
+
actorDisplayName: body.actorDisplayName,
|
|
691
|
+
}),
|
|
692
|
+
slackActorTimezone,
|
|
693
|
+
);
|
|
568
694
|
|
|
569
695
|
const diskPressureDecision = classifyDiskPressureTurnPolicy(
|
|
570
696
|
getDiskPressureStatus(),
|
|
@@ -649,14 +775,17 @@ export async function handleChannelInbound({
|
|
|
649
775
|
// preconditions used by the standard approval interception call below.
|
|
650
776
|
const isReactionAdded = body.callbackData?.startsWith("reaction:") === true;
|
|
651
777
|
if (isReactionAdded && replyCallbackUrl && !result.duplicate) {
|
|
652
|
-
const trustCtxForReaction: TrustContext =
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
778
|
+
const trustCtxForReaction: TrustContext = attachSlackRequesterTimezone(
|
|
779
|
+
resolveTrustContext({
|
|
780
|
+
assistantId: canonicalAssistantId,
|
|
781
|
+
sourceChannel,
|
|
782
|
+
conversationExternalId,
|
|
783
|
+
actorExternalId: rawSenderId,
|
|
784
|
+
actorUsername: body.actorUsername,
|
|
785
|
+
actorDisplayName: body.actorDisplayName,
|
|
786
|
+
}),
|
|
787
|
+
slackActorTimezone,
|
|
788
|
+
);
|
|
660
789
|
|
|
661
790
|
const approvalMessageTs =
|
|
662
791
|
typeof sourceMetadata?.messageId === "string"
|
|
@@ -767,7 +896,6 @@ export async function handleChannelInbound({
|
|
|
767
896
|
typeof hint === "string" && hint.trim().length > 0,
|
|
768
897
|
)
|
|
769
898
|
: [];
|
|
770
|
-
let slackRuntimeContextNotice: string | undefined;
|
|
771
899
|
|
|
772
900
|
// Inject channel-scoped permission hints for Slack channel messages
|
|
773
901
|
if (sourceChannel === "slack") {
|
|
@@ -1043,10 +1171,24 @@ export async function handleChannelInbound({
|
|
|
1043
1171
|
// this into a `slackMeta` sub-object in the row's metadata column so
|
|
1044
1172
|
// the chronological renderer can reconstruct thread structure without
|
|
1045
1173
|
// re-fetching from Slack.
|
|
1174
|
+
const slackSpeakerTimezoneLabel =
|
|
1175
|
+
trustCtx.trustClass !== "guardian"
|
|
1176
|
+
? slackActorTimezone?.timezoneLabel
|
|
1177
|
+
: undefined;
|
|
1178
|
+
const inboundClientTimezone = resolveInboundClientTimezone({
|
|
1179
|
+
bodyClientTimezone: body.clientTimezone,
|
|
1180
|
+
sourceMetadata,
|
|
1181
|
+
conversationId: result.conversationId,
|
|
1182
|
+
});
|
|
1183
|
+
const slackTranscriptTimestampTimezone =
|
|
1184
|
+
sourceChannel === "slack"
|
|
1185
|
+
? resolveSlackTranscriptTimestampTimezone(inboundClientTimezone)
|
|
1186
|
+
: undefined;
|
|
1046
1187
|
const slackInbound =
|
|
1047
1188
|
sourceChannel === "slack"
|
|
1048
1189
|
? {
|
|
1049
1190
|
channelId: conversationExternalId,
|
|
1191
|
+
...(slackChannelName ? { channelName: slackChannelName } : {}),
|
|
1050
1192
|
channelTs: sourceMessageId ?? externalMessageId,
|
|
1051
1193
|
...(slackThreadTs ? { threadTs: slackThreadTs } : {}),
|
|
1052
1194
|
...((body.actorDisplayName ?? body.actorUsername)
|
|
@@ -1057,6 +1199,17 @@ export async function handleChannelInbound({
|
|
|
1057
1199
|
...(trustCtx.requesterExternalUserId
|
|
1058
1200
|
? { actorExternalUserId: trustCtx.requesterExternalUserId }
|
|
1059
1201
|
: {}),
|
|
1202
|
+
...buildSlackTimezoneMetadata({
|
|
1203
|
+
actorTimezone: slackActorTimezone?.timezone,
|
|
1204
|
+
actorTimezoneLabel: slackActorTimezone?.timezoneLabel,
|
|
1205
|
+
actorTimezoneOffsetSeconds:
|
|
1206
|
+
slackActorTimezone?.timezoneOffsetSeconds,
|
|
1207
|
+
timestampTimezone:
|
|
1208
|
+
slackTranscriptTimestampTimezone?.timestampTimezone,
|
|
1209
|
+
timestampTimezoneLabel:
|
|
1210
|
+
slackTranscriptTimestampTimezone?.timestampTimezoneLabel,
|
|
1211
|
+
speakerTimezoneLabel: slackSpeakerTimezoneLabel,
|
|
1212
|
+
}),
|
|
1060
1213
|
}
|
|
1061
1214
|
: undefined;
|
|
1062
1215
|
|
|
@@ -1075,13 +1228,16 @@ export async function handleChannelInbound({
|
|
|
1075
1228
|
sourceChannel === "slack" && sourceMetadata?.slackBotMentioned === true;
|
|
1076
1229
|
|
|
1077
1230
|
// ── DM cold-start backfill ──
|
|
1078
|
-
// First time a Slack DM lands in a conversation that
|
|
1079
|
-
// SLACK_DM_BACKFILL_WARM_THRESHOLD stored slackMeta
|
|
1080
|
-
// window of recent history so the agent sees prior
|
|
1081
|
-
//
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
|
|
1231
|
+
// First time a Slack DM without thread_ts lands in a conversation that
|
|
1232
|
+
// has fewer than SLACK_DM_BACKFILL_WARM_THRESHOLD stored slackMeta
|
|
1233
|
+
// messages, fetch a window of recent history so the agent sees prior
|
|
1234
|
+
// context. Threaded Slack DMs use the thread gap/delta path below so
|
|
1235
|
+
// separate app conversations do not pull unrelated whole-DM history.
|
|
1236
|
+
if (
|
|
1237
|
+
sourceChannel === "slack" &&
|
|
1238
|
+
sourceChatType === "im" &&
|
|
1239
|
+
!slackThreadTs
|
|
1240
|
+
) {
|
|
1085
1241
|
// Exclude the just-arrived webhook message from the history window —
|
|
1086
1242
|
// the normal inbound persistence path writes it separately, so
|
|
1087
1243
|
// including it here would produce duplicate user turns. Only pass a
|
|
@@ -1102,18 +1258,17 @@ export async function handleChannelInbound({
|
|
|
1102
1258
|
}
|
|
1103
1259
|
|
|
1104
1260
|
// ── Thread gap/delta backfill ──
|
|
1105
|
-
// When a Slack thread reply arrives,
|
|
1106
|
-
//
|
|
1107
|
-
// window. Initial late-join turns
|
|
1108
|
-
// plus a recent window adjacent to
|
|
1109
|
-
// a delta window after the latest
|
|
1110
|
-
// inbound ts. Awaited (mirrors the DM
|
|
1111
|
-
// agent loop dispatched immediately
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1114
|
-
// are swallowed inside the helper so they never block dispatch.
|
|
1261
|
+
// When a Slack thread reply arrives, including app/agent DMs that carry
|
|
1262
|
+
// thread_ts, compare the stored thread state with the inbound message's
|
|
1263
|
+
// ts and fetch only the bounded unseen window. Initial late-join turns
|
|
1264
|
+
// hydrate the earliest thread messages plus a recent window adjacent to
|
|
1265
|
+
// the inbound reply; later turns use a delta window after the latest
|
|
1266
|
+
// stored thread ts and before the inbound ts. Awaited (mirrors the DM
|
|
1267
|
+
// cold-start path above) so the agent loop dispatched immediately
|
|
1268
|
+
// afterwards observes hydrated context. Failures are swallowed inside
|
|
1269
|
+
// the helper so they never block dispatch.
|
|
1115
1270
|
if (slackThreadTs) {
|
|
1116
|
-
|
|
1271
|
+
await triggerSlackThreadBackfillIfNeeded({
|
|
1117
1272
|
conversationId: result.conversationId,
|
|
1118
1273
|
channelId: conversationExternalId,
|
|
1119
1274
|
threadTs: slackThreadTs,
|
|
@@ -1121,8 +1276,6 @@ export async function handleChannelInbound({
|
|
|
1121
1276
|
account: slackAccount,
|
|
1122
1277
|
guardianExternalUserId: trustCtx.guardianExternalUserId,
|
|
1123
1278
|
});
|
|
1124
|
-
const lateJoinNotice = buildSlackLateJoinNotice(backfillResult);
|
|
1125
|
-
if (lateJoinNotice) slackRuntimeContextNotice = lateJoinNotice;
|
|
1126
1279
|
}
|
|
1127
1280
|
|
|
1128
1281
|
// Wrap non-guardian inbound content in external_content boundaries so
|
|
@@ -1155,7 +1308,6 @@ export async function handleChannelInbound({
|
|
|
1155
1308
|
externalChatId: conversationExternalId,
|
|
1156
1309
|
trustCtx,
|
|
1157
1310
|
metadataHints,
|
|
1158
|
-
slackRuntimeContextNotice,
|
|
1159
1311
|
metadataUxBrief,
|
|
1160
1312
|
commandIntent,
|
|
1161
1313
|
sourceLanguageCode,
|
|
@@ -1163,6 +1315,7 @@ export async function handleChannelInbound({
|
|
|
1163
1315
|
assistantId: canonicalAssistantId,
|
|
1164
1316
|
approvalCopyGenerator,
|
|
1165
1317
|
chatType: sourceChatType,
|
|
1318
|
+
clientTimezone: inboundClientTimezone,
|
|
1166
1319
|
slackBotMentioned,
|
|
1167
1320
|
slackInbound,
|
|
1168
1321
|
});
|
|
@@ -1448,9 +1601,10 @@ function readStoredSlackThreadState(
|
|
|
1448
1601
|
*
|
|
1449
1602
|
* Shared insertion point for any path that hydrates Slack history lazily
|
|
1450
1603
|
* (DM cold-start backfill, thread gap/delta backfill, etc.). Backfilled Slack
|
|
1451
|
-
* rows
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1604
|
+
* rows normally persist as `user` history, but rows authored by this
|
|
1605
|
+
* assistant's configured Slack bot are replayed as assistant history so prior
|
|
1606
|
+
* assistant messages do not enter model context wrapped as external user
|
|
1607
|
+
* content.
|
|
1454
1608
|
* Caller is responsible for dedup checks before invoking; this helper
|
|
1455
1609
|
* performs no idempotency check itself.
|
|
1456
1610
|
*/
|
|
@@ -1473,6 +1627,17 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1473
1627
|
...(f.mimetype ? { mimetype: f.mimetype } : {}),
|
|
1474
1628
|
}));
|
|
1475
1629
|
const actorExternalUserId = message.sender?.id?.trim();
|
|
1630
|
+
const actorTimezone = trimMetadataString(message.metadata, "actorTimezone");
|
|
1631
|
+
const actorTimezoneLabel = trimMetadataString(
|
|
1632
|
+
message.metadata,
|
|
1633
|
+
"actorTimezoneLabel",
|
|
1634
|
+
);
|
|
1635
|
+
const isGuardian = isBackfilledSlackGuardianMessage(
|
|
1636
|
+
message,
|
|
1637
|
+
params.guardianExternalUserId,
|
|
1638
|
+
);
|
|
1639
|
+
const slackTranscriptTimestampTimezone =
|
|
1640
|
+
resolveSlackTranscriptTimestampTimezone();
|
|
1476
1641
|
const slackMeta: SlackMessageMetadata = {
|
|
1477
1642
|
source: "slack",
|
|
1478
1643
|
channelId: params.channelId,
|
|
@@ -1481,14 +1646,24 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1481
1646
|
...(message.threadId ? { threadTs: message.threadId } : {}),
|
|
1482
1647
|
...(message.sender?.name ? { displayName: message.sender.name } : {}),
|
|
1483
1648
|
...(actorExternalUserId ? { actorExternalUserId } : {}),
|
|
1649
|
+
...buildSlackTimezoneMetadata({
|
|
1650
|
+
actorTimezone,
|
|
1651
|
+
actorTimezoneLabel,
|
|
1652
|
+
actorTimezoneOffsetSeconds: message.metadata?.actorTimezoneOffsetSeconds,
|
|
1653
|
+
timestampTimezone: slackTranscriptTimestampTimezone?.timestampTimezone,
|
|
1654
|
+
timestampTimezoneLabel:
|
|
1655
|
+
slackTranscriptTimestampTimezone?.timestampTimezoneLabel,
|
|
1656
|
+
speakerTimezoneLabel: isGuardian ? undefined : actorTimezoneLabel,
|
|
1657
|
+
}),
|
|
1484
1658
|
...(slackFiles.length > 0 ? { slackFiles } : {}),
|
|
1485
1659
|
};
|
|
1486
1660
|
|
|
1487
|
-
const
|
|
1661
|
+
const role = (await isBackfilledSlackAssistantMessage(
|
|
1488
1662
|
message,
|
|
1489
|
-
params.
|
|
1490
|
-
)
|
|
1491
|
-
|
|
1663
|
+
params.account,
|
|
1664
|
+
))
|
|
1665
|
+
? "assistant"
|
|
1666
|
+
: "user";
|
|
1492
1667
|
|
|
1493
1668
|
const rawText = message.text ?? "";
|
|
1494
1669
|
|
|
@@ -1620,6 +1795,67 @@ function isBackfilledSlackGuardianMessage(
|
|
|
1620
1795
|
return canonicalSender === canonicalGuardian;
|
|
1621
1796
|
}
|
|
1622
1797
|
|
|
1798
|
+
const SLACK_ASSISTANT_THREAD_PLACEHOLDER_TEXT = "New Assistant Thread";
|
|
1799
|
+
|
|
1800
|
+
async function isSlackAssistantThreadPlaceholder(
|
|
1801
|
+
message: ProviderMessage,
|
|
1802
|
+
account: string | undefined,
|
|
1803
|
+
): Promise<boolean> {
|
|
1804
|
+
if (message.metadata?.isBot !== true) return false;
|
|
1805
|
+
const hasSlackFiles =
|
|
1806
|
+
Array.isArray(message.metadata.slackFiles) &&
|
|
1807
|
+
message.metadata.slackFiles.length > 0;
|
|
1808
|
+
return (
|
|
1809
|
+
message.text.replace(/\s+/g, " ").trim() ===
|
|
1810
|
+
SLACK_ASSISTANT_THREAD_PLACEHOLDER_TEXT &&
|
|
1811
|
+
(message.threadId === undefined || message.threadId === message.id) &&
|
|
1812
|
+
message.hasAttachments !== true &&
|
|
1813
|
+
!hasSlackFiles &&
|
|
1814
|
+
(await isBackfilledSlackAssistantMessage(message, account))
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
async function isBackfilledSlackAssistantMessage(
|
|
1819
|
+
message: ProviderMessage,
|
|
1820
|
+
account: string | undefined,
|
|
1821
|
+
): Promise<boolean> {
|
|
1822
|
+
if (message.metadata?.isBot !== true) return false;
|
|
1823
|
+
|
|
1824
|
+
const botUserId = getConfig().slack.botUserId.trim();
|
|
1825
|
+
const rawSenderId = message.sender?.id?.trim();
|
|
1826
|
+
if (!botUserId) return false;
|
|
1827
|
+
|
|
1828
|
+
if (rawSenderId && slackIdentityMatches(rawSenderId, botUserId)) return true;
|
|
1829
|
+
|
|
1830
|
+
const rawBotId =
|
|
1831
|
+
typeof message.metadata.slackBotId === "string"
|
|
1832
|
+
? message.metadata.slackBotId.trim()
|
|
1833
|
+
: "";
|
|
1834
|
+
if (!rawBotId) return false;
|
|
1835
|
+
|
|
1836
|
+
try {
|
|
1837
|
+
const resolvedBotUserId = await resolveSlackBotUserId(account, rawBotId);
|
|
1838
|
+
return (
|
|
1839
|
+
typeof resolvedBotUserId === "string" &&
|
|
1840
|
+
slackIdentityMatches(resolvedBotUserId, botUserId)
|
|
1841
|
+
);
|
|
1842
|
+
} catch (err) {
|
|
1843
|
+
log.warn(
|
|
1844
|
+
{ err, slackBotId: rawBotId, channelTs: message.id },
|
|
1845
|
+
"Failed to resolve Slack bot id for backfilled assistant detection",
|
|
1846
|
+
);
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function slackIdentityMatches(left: string, right: string): boolean {
|
|
1852
|
+
const canonicalSender =
|
|
1853
|
+
canonicalizeInboundIdentity("slack", left) ?? left.trim();
|
|
1854
|
+
const canonicalBot =
|
|
1855
|
+
canonicalizeInboundIdentity("slack", right) ?? right.trim();
|
|
1856
|
+
return canonicalSender === canonicalBot;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1623
1859
|
/**
|
|
1624
1860
|
* Transient view of `slackFiles` that preserves the download URLs added by
|
|
1625
1861
|
* `mapSlackFiles` on the in-flight `ProviderMessage`. These URLs never reach
|
|
@@ -1745,6 +1981,9 @@ async function runBackfillSlackDmIfCold(params: {
|
|
|
1745
1981
|
const ordered = [...fetched].reverse();
|
|
1746
1982
|
for (const message of ordered) {
|
|
1747
1983
|
if (seen.has(message.id)) continue;
|
|
1984
|
+
if (await isSlackAssistantThreadPlaceholder(message, params.account)) {
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1748
1987
|
try {
|
|
1749
1988
|
await persistBackfilledSlackMessage({
|
|
1750
1989
|
conversationId: params.conversationId,
|
|
@@ -2168,18 +2407,6 @@ function sortSlackProviderMessages(
|
|
|
2168
2407
|
});
|
|
2169
2408
|
}
|
|
2170
2409
|
|
|
2171
|
-
function buildSlackLateJoinNotice(
|
|
2172
|
-
result: SlackThreadBackfillResult,
|
|
2173
|
-
): string | null {
|
|
2174
|
-
if (result.reason !== "thread_late_join" || result.persisted === 0) {
|
|
2175
|
-
return null;
|
|
2176
|
-
}
|
|
2177
|
-
const omitted = result.omittedMiddle
|
|
2178
|
-
? " Some middle thread messages were intentionally omitted from this turn's hydrated context to keep latency bounded."
|
|
2179
|
-
: "";
|
|
2180
|
-
return `Slack context note: this turn joined an existing thread. ${result.persisted} earlier thread message${result.persisted === 1 ? " was" : "s were"} backfilled before the current message.${omitted}`;
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
2410
|
/**
|
|
2184
2411
|
* Lazily backfill Slack thread gaps for an inbound thread reply.
|
|
2185
2412
|
*
|
|
@@ -2320,6 +2547,9 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
2320
2547
|
for (const message of fetched) {
|
|
2321
2548
|
if (!message.id) continue;
|
|
2322
2549
|
if (threadState.storedChannelTs.has(message.id)) continue;
|
|
2550
|
+
if (await isSlackAssistantThreadPlaceholder(message, account)) {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2323
2553
|
try {
|
|
2324
2554
|
await persistBackfilledSlackMessage({
|
|
2325
2555
|
conversationId,
|