@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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure chronological
|
|
2
|
+
* Pure chronological transcript rendering for Slack transcripts.
|
|
3
3
|
*
|
|
4
4
|
* Given a list of stored messages (post-upgrade rows with structured metadata
|
|
5
5
|
* AND legacy pre-upgrade rows with `metadata === null`), produces a flat
|
|
6
|
-
* `{role, content}[]` chronologically ordered with compact
|
|
7
|
-
*
|
|
6
|
+
* `{role, content}[]` chronologically ordered with compact Slack tags so the
|
|
7
|
+
* model can reason across sibling threads in one channel.
|
|
8
8
|
*
|
|
9
9
|
* The function is pure: no I/O, no implicit clock reads. Time is taken from
|
|
10
10
|
* `opts.now` only when needed for relative formatting. Sort and tag rendering
|
|
@@ -21,7 +21,10 @@ import {
|
|
|
21
21
|
parseExternalContentEnvelope,
|
|
22
22
|
wrapUntrustedContent,
|
|
23
23
|
} from "../../../security/untrusted-content.js";
|
|
24
|
-
import
|
|
24
|
+
import {
|
|
25
|
+
formatSlackTimezoneLabel,
|
|
26
|
+
type SlackMessageMetadata,
|
|
27
|
+
} from "./message-metadata.js";
|
|
25
28
|
|
|
26
29
|
export interface RenderableSlackMessage {
|
|
27
30
|
role: "user" | "assistant";
|
|
@@ -78,8 +81,15 @@ export interface RenderedSlackTranscript {
|
|
|
78
81
|
export interface RenderedSlackTranscriptMessage {
|
|
79
82
|
readonly message: Message;
|
|
80
83
|
readonly sourceChannelTs: string | null;
|
|
84
|
+
/** How the first rendered text line got its Slack attribution, if any. */
|
|
85
|
+
readonly tagLineProvenance: RenderedSlackTranscriptTagLineProvenance;
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
export type RenderedSlackTranscriptTagLineProvenance =
|
|
89
|
+
| "none"
|
|
90
|
+
| "slack-reaction"
|
|
91
|
+
| "slack-timezone-message";
|
|
92
|
+
|
|
83
93
|
/**
|
|
84
94
|
* Replayable Anthropic content-block types that we preserve verbatim from a
|
|
85
95
|
* persisted row when rendering the Slack chronological transcript.
|
|
@@ -117,31 +127,6 @@ export function parentAlias(channelTs: string): string {
|
|
|
117
127
|
return `M${hash.slice(0, 6)}`;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
/**
|
|
121
|
-
* Trailing signature of a reaction or reaction-overflow line, both of
|
|
122
|
-
* which end with a `parentAlias` target and a closing bracket:
|
|
123
|
-
* `[... reacted 👍 to M1a2b3c]`, `[... removed 👍 from M1a2b3c]`,
|
|
124
|
-
* `[…and N more reactions to M1a2b3c]`. Regular message tag lines end
|
|
125
|
-
* with the message body, not with `]`, so they never match.
|
|
126
|
-
*/
|
|
127
|
-
const REACTION_TAG_LINE_SUFFIX = /M[0-9a-f]{6}\]$/;
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Whether a rendered tag-line string was produced by the reaction or
|
|
131
|
-
* reaction-overflow code paths (`renderReaction` / the overflow trailer).
|
|
132
|
-
*
|
|
133
|
-
* Reaction lines already embed the actor attribution inline
|
|
134
|
-
* (`[11/14/23 14:28 @assistant reacted 👍 to M1a2b3c]`), so consumers
|
|
135
|
-
* that flatten the rendered transcript and re-apply role labels should
|
|
136
|
-
* skip these lines to avoid double-attribution.
|
|
137
|
-
*
|
|
138
|
-
* Co-located with `renderReaction` and `parentAlias` so the format
|
|
139
|
-
* knowledge lives with the functions that own the line shape.
|
|
140
|
-
*/
|
|
141
|
-
export function isReactionTagLine(text: string): boolean {
|
|
142
|
-
return REACTION_TAG_LINE_SUFFIX.test(text);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
130
|
/**
|
|
146
131
|
* Format a Slack ts (`"1700000000.000100"`) as `MM/DD/YY HH:MM` (UTC).
|
|
147
132
|
*
|
|
@@ -168,6 +153,112 @@ function formatEpochMs(ms: number): string {
|
|
|
168
153
|
return `${mo}/${da}/${yy} ${hh}:${mm}`;
|
|
169
154
|
}
|
|
170
155
|
|
|
156
|
+
const compactDateTimeFormatters = new Map<string, Intl.DateTimeFormat>();
|
|
157
|
+
|
|
158
|
+
function getCompactDateTimeFormatter(timeZone: string): Intl.DateTimeFormat {
|
|
159
|
+
let formatter = compactDateTimeFormatters.get(timeZone);
|
|
160
|
+
if (!formatter) {
|
|
161
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
162
|
+
timeZone,
|
|
163
|
+
month: "short",
|
|
164
|
+
day: "numeric",
|
|
165
|
+
year: "numeric",
|
|
166
|
+
hour: "numeric",
|
|
167
|
+
minute: "2-digit",
|
|
168
|
+
hour12: true,
|
|
169
|
+
});
|
|
170
|
+
compactDateTimeFormatters.set(timeZone, formatter);
|
|
171
|
+
}
|
|
172
|
+
return formatter;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function compactDateTimeParts(ms: number, timeZone: string) {
|
|
176
|
+
try {
|
|
177
|
+
const parts = getCompactDateTimeFormatter(timeZone).formatToParts(
|
|
178
|
+
new Date(ms),
|
|
179
|
+
);
|
|
180
|
+
const get = (type: Intl.DateTimeFormatPartTypes) =>
|
|
181
|
+
parts.find((part) => part.type === type)?.value;
|
|
182
|
+
const month = get("month")?.toLowerCase();
|
|
183
|
+
const day = get("day");
|
|
184
|
+
const year = get("year");
|
|
185
|
+
const hour = get("hour");
|
|
186
|
+
const minute = get("minute");
|
|
187
|
+
const dayPeriod = get("dayPeriod");
|
|
188
|
+
if (!month || !day || !year || !hour || !minute || !dayPeriod) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return { month, day, year, hour, minute, dayPeriod };
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function formatCompactEpochMs(
|
|
198
|
+
ms: number,
|
|
199
|
+
timeZone: string,
|
|
200
|
+
timezoneLabel: string | undefined,
|
|
201
|
+
): string {
|
|
202
|
+
if (!Number.isFinite(ms)) {
|
|
203
|
+
return `??? ?? ???? ??:?? ${
|
|
204
|
+
formatSlackTimezoneLabel(timeZone, {
|
|
205
|
+
persistedLabel: timezoneLabel,
|
|
206
|
+
nowMs: ms,
|
|
207
|
+
}) ?? timeZone
|
|
208
|
+
}`;
|
|
209
|
+
}
|
|
210
|
+
const parts = compactDateTimeParts(ms, timeZone);
|
|
211
|
+
if (!parts) return formatEpochMs(ms);
|
|
212
|
+
const label =
|
|
213
|
+
formatSlackTimezoneLabel(timeZone, {
|
|
214
|
+
persistedLabel: timezoneLabel,
|
|
215
|
+
nowMs: ms,
|
|
216
|
+
}) ?? timeZone;
|
|
217
|
+
return `${parts.month} ${parts.day} ${parts.year} ${parts.hour}:${parts.minute} ${parts.dayPeriod} ${label}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function formatCompactSlackTs(
|
|
221
|
+
channelTs: string,
|
|
222
|
+
timeZone: string,
|
|
223
|
+
timezoneLabel: string | undefined,
|
|
224
|
+
): string {
|
|
225
|
+
const seconds = Number.parseFloat(channelTs);
|
|
226
|
+
if (!Number.isFinite(seconds)) {
|
|
227
|
+
return `??? ?? ???? ??:?? ${
|
|
228
|
+
formatSlackTimezoneLabel(timeZone, {
|
|
229
|
+
persistedLabel: timezoneLabel,
|
|
230
|
+
nowMs: Number.NaN,
|
|
231
|
+
}) ?? timeZone
|
|
232
|
+
}`;
|
|
233
|
+
}
|
|
234
|
+
return formatCompactEpochMs(seconds * 1000, timeZone, timezoneLabel);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function hasTimestampTimezone(
|
|
238
|
+
meta: SlackMessageMetadata | null,
|
|
239
|
+
): meta is SlackMessageMetadata & { timestampTimezone: string } {
|
|
240
|
+
return (
|
|
241
|
+
typeof meta?.timestampTimezone === "string" &&
|
|
242
|
+
meta.timestampTimezone.trim().length > 0
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function speakerLabel(
|
|
247
|
+
msg: RenderableSlackMessage,
|
|
248
|
+
meta: SlackMessageMetadata,
|
|
249
|
+
): string {
|
|
250
|
+
const speaker =
|
|
251
|
+
msg.senderLabel ?? (msg.role === "assistant" ? "assistant" : "");
|
|
252
|
+
const suffix =
|
|
253
|
+
msg.role === "assistant"
|
|
254
|
+
? null
|
|
255
|
+
: formatSlackTimezoneLabel(undefined, {
|
|
256
|
+
persistedLabel: meta.speakerTimezoneLabel,
|
|
257
|
+
});
|
|
258
|
+
if (!speaker) return "";
|
|
259
|
+
return suffix ? `${speaker} (${suffix})` : speaker;
|
|
260
|
+
}
|
|
261
|
+
|
|
171
262
|
function renderSlackFileMarkers(
|
|
172
263
|
files: SlackMessageMetadata["slackFiles"],
|
|
173
264
|
): string {
|
|
@@ -242,37 +333,62 @@ function maxNullableSlackTs(a: string | null, b: string | null): string | null {
|
|
|
242
333
|
* is preserved without carrying a mimickable timestamp.
|
|
243
334
|
*
|
|
244
335
|
* Tradeoffs deliberately accepted by this simplification:
|
|
245
|
-
* - Thread arrows (`→ Mxxxxxx`) are dropped from
|
|
246
|
-
* common single-thread-at-a-time case
|
|
247
|
-
* adjacency
|
|
248
|
-
*
|
|
249
|
-
* assistant is fielding two thread conversations in parallel and the
|
|
250
|
-
* model has to disambiguate which reply lands in which thread from the
|
|
251
|
-
* full chronological transcript — the bracketed arrow previously carried
|
|
252
|
-
* that signal and is now absent. The `<active_thread>` focus block
|
|
253
|
-
* (single thread by construction) is unaffected.
|
|
336
|
+
* - Thread arrows (`→ Mxxxxxx`) are dropped from message-row tag lines. The
|
|
337
|
+
* common single-thread-at-a-time case still has role alternation +
|
|
338
|
+
* chronological adjacency, and the `<active_thread>` focus block remains a
|
|
339
|
+
* single-thread view by construction.
|
|
254
340
|
* - Edited assistant rows render only the latest content, not an edit
|
|
255
341
|
* marker. Edits are rare for the assistant and the latest content is the
|
|
256
342
|
* only replayable signal anyway.
|
|
257
343
|
*
|
|
258
|
-
* Any alternative "subtle" marker
|
|
259
|
-
*
|
|
260
|
-
*
|
|
344
|
+
* Any alternative "subtle" assistant marker would reintroduce a consistent,
|
|
345
|
+
* mimickable prefix pattern — the very problem this function is designed to
|
|
346
|
+
* avoid — so we keep the content-only form.
|
|
261
347
|
*/
|
|
262
348
|
function renderMessage(msg: RenderableSlackMessage): string {
|
|
263
|
-
|
|
349
|
+
const meta = msg.metadata;
|
|
350
|
+
|
|
351
|
+
if (msg.role === "assistant" && !hasTimestampTimezone(meta)) {
|
|
264
352
|
if (msg.metadata?.deletedAt !== undefined) return "[deleted]";
|
|
265
353
|
return appendSlackFileMarkers(msg.content, msg.metadata?.slackFiles);
|
|
266
354
|
}
|
|
267
355
|
|
|
268
|
-
const meta = msg.metadata;
|
|
269
356
|
const senderPart = msg.senderLabel ? ` ${msg.senderLabel}` : "";
|
|
270
357
|
if (!meta) {
|
|
271
|
-
// Legacy pre-upgrade row: flat render, no
|
|
358
|
+
// Legacy pre-upgrade row: flat render, no Slack metadata-derived fields.
|
|
272
359
|
const time = formatEpochMs(msg.createdAt);
|
|
273
360
|
return `[${time}${senderPart}]: ${renderModelBodyWithSlackFiles(msg, undefined)}`;
|
|
274
361
|
}
|
|
275
362
|
|
|
363
|
+
if (hasTimestampTimezone(meta)) {
|
|
364
|
+
const time = formatCompactSlackTs(
|
|
365
|
+
meta.channelTs,
|
|
366
|
+
meta.timestampTimezone,
|
|
367
|
+
meta.timestampTimezoneLabel,
|
|
368
|
+
);
|
|
369
|
+
const speaker = speakerLabel(msg, meta);
|
|
370
|
+
const speakerPart = speaker ? ` ${speaker}` : "";
|
|
371
|
+
if (meta.deletedAt !== undefined) {
|
|
372
|
+
const dtime = formatCompactEpochMs(
|
|
373
|
+
meta.deletedAt,
|
|
374
|
+
meta.timestampTimezone,
|
|
375
|
+
meta.timestampTimezoneLabel,
|
|
376
|
+
);
|
|
377
|
+
return `[${time}${speakerPart} - deleted ${dtime}]`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
let head = `[${time}${speakerPart}`;
|
|
381
|
+
if (meta.editedAt !== undefined) {
|
|
382
|
+
head += `, edited ${formatCompactEpochMs(
|
|
383
|
+
meta.editedAt,
|
|
384
|
+
meta.timestampTimezone,
|
|
385
|
+
meta.timestampTimezoneLabel,
|
|
386
|
+
)}`;
|
|
387
|
+
}
|
|
388
|
+
head += `] ${renderModelBodyWithSlackFiles(msg, meta.slackFiles)}`;
|
|
389
|
+
return head;
|
|
390
|
+
}
|
|
391
|
+
|
|
276
392
|
const time = formatSlackTs(meta.channelTs);
|
|
277
393
|
|
|
278
394
|
if (meta.deletedAt !== undefined) {
|
|
@@ -281,9 +397,6 @@ function renderMessage(msg: RenderableSlackMessage): string {
|
|
|
281
397
|
}
|
|
282
398
|
|
|
283
399
|
let head = `[${time}${senderPart}`;
|
|
284
|
-
if (meta.threadTs && meta.threadTs !== meta.channelTs) {
|
|
285
|
-
head += ` → ${parentAlias(meta.threadTs)}`;
|
|
286
|
-
}
|
|
287
400
|
if (meta.editedAt !== undefined) {
|
|
288
401
|
head += `, edited ${formatEpochMs(meta.editedAt)}`;
|
|
289
402
|
}
|
|
@@ -343,7 +456,13 @@ function renderModelBody(msg: RenderableSlackMessage, body: string): string {
|
|
|
343
456
|
function renderReaction(msg: RenderableSlackMessage): string | null {
|
|
344
457
|
const meta = msg.metadata;
|
|
345
458
|
if (!meta || meta.eventKind !== "reaction" || !meta.reaction) return null;
|
|
346
|
-
const time =
|
|
459
|
+
const time = hasTimestampTimezone(meta)
|
|
460
|
+
? formatCompactSlackTs(
|
|
461
|
+
meta.channelTs,
|
|
462
|
+
meta.timestampTimezone,
|
|
463
|
+
meta.timestampTimezoneLabel,
|
|
464
|
+
)
|
|
465
|
+
: formatSlackTs(meta.channelTs);
|
|
347
466
|
const actor =
|
|
348
467
|
msg.senderLabel ?? (msg.role === "assistant" ? "@assistant" : "@user");
|
|
349
468
|
const verb = meta.reaction.op === "added" ? "reacted" : "removed";
|
|
@@ -444,7 +563,7 @@ function buildMessageContentBlocks(
|
|
|
444
563
|
}
|
|
445
564
|
|
|
446
565
|
/**
|
|
447
|
-
* Render a chronological transcript with compact
|
|
566
|
+
* Render a chronological transcript with compact Slack tags.
|
|
448
567
|
*
|
|
449
568
|
* Sort is stable: messages with identical sort keys preserve their input
|
|
450
569
|
* order so callers controlling input ordering can break ties deterministically.
|
|
@@ -517,6 +636,7 @@ export function renderSlackTranscriptWithProvenance(
|
|
|
517
636
|
],
|
|
518
637
|
},
|
|
519
638
|
sourceChannelTs: acc.sourceChannelTs,
|
|
639
|
+
tagLineProvenance: "slack-reaction",
|
|
520
640
|
});
|
|
521
641
|
|
|
522
642
|
const flushOverflowExcept = (
|
|
@@ -550,6 +670,7 @@ export function renderSlackTranscriptWithProvenance(
|
|
|
550
670
|
content: [{ type: "text" as const, text: line }],
|
|
551
671
|
},
|
|
552
672
|
sourceChannelTs: meta.channelTs,
|
|
673
|
+
tagLineProvenance: "slack-reaction",
|
|
553
674
|
});
|
|
554
675
|
}
|
|
555
676
|
} else {
|
|
@@ -575,12 +696,17 @@ export function renderSlackTranscriptWithProvenance(
|
|
|
575
696
|
const tagLine = renderMessage(m);
|
|
576
697
|
const blocks = buildMessageContentBlocks(m, tagLine);
|
|
577
698
|
if (blocks.length === 0) continue;
|
|
699
|
+
const hasRenderedText = blocks.some((block) => block.type === "text");
|
|
578
700
|
out.push({
|
|
579
701
|
message: {
|
|
580
702
|
role: m.role,
|
|
581
703
|
content: blocks,
|
|
582
704
|
},
|
|
583
705
|
sourceChannelTs: meta?.channelTs ?? null,
|
|
706
|
+
tagLineProvenance:
|
|
707
|
+
hasRenderedText && hasTimestampTimezone(meta)
|
|
708
|
+
? "slack-timezone-message"
|
|
709
|
+
: "none",
|
|
584
710
|
});
|
|
585
711
|
}
|
|
586
712
|
|
|
@@ -645,6 +771,7 @@ function filterOrphanToolPairs(
|
|
|
645
771
|
out.push({
|
|
646
772
|
message: { role: msg.role, content: kept },
|
|
647
773
|
sourceChannelTs: entry.sourceChannelTs,
|
|
774
|
+
tagLineProvenance: entry.tagLineProvenance,
|
|
648
775
|
});
|
|
649
776
|
}
|
|
650
777
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
type CallSlackApi = (
|
|
4
|
+
method: string,
|
|
5
|
+
body: Record<string, unknown>,
|
|
6
|
+
) => Promise<Record<string, unknown>>;
|
|
7
|
+
|
|
8
|
+
const callSlackApiMock = mock<CallSlackApi>(async () => ({ ok: true }));
|
|
9
|
+
|
|
10
|
+
mock.module("../../../util/logger.js", () => ({
|
|
11
|
+
getLogger: () =>
|
|
12
|
+
new Proxy({} as Record<string, unknown>, {
|
|
13
|
+
get: () => () => {},
|
|
14
|
+
}),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
mock.module("./api.js", () => ({
|
|
18
|
+
callSlackApi: (method: string, body: Record<string, unknown>) =>
|
|
19
|
+
callSlackApiMock(method, body),
|
|
20
|
+
callSlackApiForm: async () => ({}),
|
|
21
|
+
completeSlackUpload: async () => {},
|
|
22
|
+
SlackApiError: class SlackApiError extends Error {
|
|
23
|
+
slackError?: string;
|
|
24
|
+
},
|
|
25
|
+
uploadToSlackUrl: async () => {},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import { sendSlackAssistantThreadStatus } from "./send.js";
|
|
29
|
+
|
|
30
|
+
describe("sendSlackAssistantThreadStatus", () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
callSlackApiMock.mockReset();
|
|
33
|
+
callSlackApiMock.mockImplementation(async () => ({ ok: true }));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("serializes loading messages for Slack assistant thread status", async () => {
|
|
37
|
+
await sendSlackAssistantThreadStatus(
|
|
38
|
+
"C123",
|
|
39
|
+
"1700000000.000100",
|
|
40
|
+
"is working...",
|
|
41
|
+
["Reading files", "Running tests"],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(callSlackApiMock).toHaveBeenCalledTimes(1);
|
|
45
|
+
expect(callSlackApiMock).toHaveBeenCalledWith(
|
|
46
|
+
"assistant.threads.setStatus",
|
|
47
|
+
{
|
|
48
|
+
channel_id: "C123",
|
|
49
|
+
thread_ts: "1700000000.000100",
|
|
50
|
+
status: "is working...",
|
|
51
|
+
loading_messages: ["Reading files", "Running tests"],
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("falls back to the reaction path when status API delivery fails", async () => {
|
|
57
|
+
callSlackApiMock
|
|
58
|
+
.mockImplementationOnce(async () => {
|
|
59
|
+
throw new Error("missing_scope");
|
|
60
|
+
})
|
|
61
|
+
.mockImplementationOnce(async () => ({ ok: true }));
|
|
62
|
+
|
|
63
|
+
await sendSlackAssistantThreadStatus(
|
|
64
|
+
"C123",
|
|
65
|
+
"1700000000.000100",
|
|
66
|
+
"is working...",
|
|
67
|
+
["Reading files"],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(callSlackApiMock).toHaveBeenCalledTimes(2);
|
|
71
|
+
expect(callSlackApiMock).toHaveBeenNthCalledWith(2, "reactions.add", {
|
|
72
|
+
channel: "C123",
|
|
73
|
+
name: "eyes",
|
|
74
|
+
timestamp: "1700000000.000100",
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -275,13 +275,19 @@ export async function sendSlackAssistantThreadStatus(
|
|
|
275
275
|
channel: string,
|
|
276
276
|
threadTs: string,
|
|
277
277
|
status: string,
|
|
278
|
+
loadingMessages?: string[],
|
|
278
279
|
): Promise<void> {
|
|
279
280
|
try {
|
|
280
|
-
|
|
281
|
+
const body: Record<string, unknown> = {
|
|
281
282
|
channel_id: channel,
|
|
282
283
|
thread_ts: threadTs,
|
|
283
284
|
status,
|
|
284
|
-
}
|
|
285
|
+
};
|
|
286
|
+
if (loadingMessages !== undefined) {
|
|
287
|
+
body.loading_messages = loadingMessages;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await callSlackApi("assistant.threads.setStatus", body);
|
|
285
291
|
return;
|
|
286
292
|
} catch {
|
|
287
293
|
log.warn(
|
|
@@ -13,6 +13,17 @@ export interface SlackAuthTestResponse extends SlackApiResponse {
|
|
|
13
13
|
user_id: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface SlackBotsInfoResponse extends SlackApiResponse {
|
|
17
|
+
bot: {
|
|
18
|
+
id: string;
|
|
19
|
+
user_id?: string;
|
|
20
|
+
app_id?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
deleted?: boolean;
|
|
23
|
+
updated?: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
export interface SlackConversation {
|
|
17
28
|
id: string;
|
|
18
29
|
name?: string;
|
|
@@ -78,6 +89,9 @@ export interface SlackUser {
|
|
|
78
89
|
id: string;
|
|
79
90
|
name: string;
|
|
80
91
|
real_name?: string;
|
|
92
|
+
tz?: string;
|
|
93
|
+
tz_label?: string;
|
|
94
|
+
tz_offset?: number;
|
|
81
95
|
profile?: {
|
|
82
96
|
display_name?: string;
|
|
83
97
|
real_name?: string;
|
|
@@ -159,7 +159,10 @@ describe("emitNotificationSignal home-feed wire-up", () => {
|
|
|
159
159
|
expect(appended.id).toBe(`notif:${result.signalId}`);
|
|
160
160
|
expect(appended.title).toBe("Background job done");
|
|
161
161
|
expect(appended.summary).toBe("Summary of what happened.");
|
|
162
|
-
|
|
162
|
+
// The feed item's conversationId points to the source conversation
|
|
163
|
+
// that emitted the signal, not the conversation the notification
|
|
164
|
+
// pipeline spawned to handle it.
|
|
165
|
+
expect(appended.conversationId).toBe("conv-source-1");
|
|
163
166
|
});
|
|
164
167
|
|
|
165
168
|
test("interactive standard conversation does NOT trigger appendFeedItem", async () => {
|