@vellumai/assistant 0.11.5 → 0.11.6-staging.1
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/AGENTS.md +5 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +70 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +16 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +6 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +121 -61
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/openapi.yaml +421 -15
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/app-pin-store.test.ts +149 -0
- package/src/__tests__/channel-availability-routes.test.ts +23 -1
- package/src/__tests__/channel-readiness-discord.test.ts +231 -0
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +141 -0
- package/src/__tests__/channel-reply-delivery.test.ts +4 -4
- package/src/__tests__/client-os-metadata-persistence.test.ts +23 -10
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-seed-composer.test.ts +8 -0
- package/src/__tests__/conversation-slash-commands.test.ts +8 -0
- package/src/__tests__/disk-pressure-policy.test.ts +6 -0
- package/src/__tests__/gemini-provider.test.ts +138 -0
- package/src/__tests__/history-repair.test.ts +105 -3
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +45 -0
- package/src/__tests__/migration-import-from-path.test.ts +349 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +102 -0
- package/src/__tests__/oauth-commands-routes.test.ts +89 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +7 -6
- package/src/__tests__/openai-provider.test.ts +18 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/platform-callback-registration.test.ts +184 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +71 -3
- package/src/__tests__/pricing.test.ts +2 -2
- package/src/__tests__/public-ingress-urls.test.ts +36 -0
- package/src/__tests__/resolve-trust-class.test.ts +0 -48
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/agent/history-repair/history-repair.ts +45 -14
- package/src/agent/loop.ts +4 -1
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +11 -0
- package/src/apps/app-pin-reconciler.ts +92 -0
- package/src/apps/app-pin-store.ts +125 -0
- package/src/channels/gateway-channel-socket-health.ts +32 -0
- package/src/channels/gateway-discord-admission.ts +32 -0
- package/src/channels/types.ts +20 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +67 -2
- package/src/cli/commands/channels/__tests__/channels.test.ts +85 -0
- package/src/cli/commands/channels/index.ts +45 -31
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +28 -2
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/request.test.ts +290 -0
- package/src/cli/commands/oauth/request.ts +57 -41
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/conversation-error.ts +24 -2
- package/src/daemon/conversation-slash.ts +6 -15
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/disk-pressure-policy.ts +7 -1
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +26 -0
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/trust-context.ts +0 -37
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/platform-callback-registration.ts +28 -2
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +25 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +8 -2
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +212 -10
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +5 -2
- package/src/live-voice/live-voice-connection.ts +46 -8
- package/src/live-voice/live-voice-manager.ts +25 -0
- package/src/live-voice/live-voice-session-manager.ts +318 -2
- package/src/live-voice/live-voice-session.ts +52 -2
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +126 -68
- package/src/messaging/providers/channel-transport.ts +64 -47
- package/src/messaging/providers/discord/send.test.ts +46 -1
- package/src/messaging/providers/discord/send.ts +51 -0
- package/src/messaging/providers/discord/transport.ts +26 -3
- package/src/messaging/providers/index.ts +22 -47
- package/src/messaging/providers/slack/send.test.ts +83 -26
- package/src/messaging/providers/slack/send.ts +120 -51
- package/src/messaging/providers/slack/stream-tasks.test.ts +26 -0
- package/src/messaging/providers/slack/stream-tasks.ts +39 -0
- package/src/messaging/providers/slack/transport.ts +24 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +109 -12
- package/src/messaging/providers/telegram-bot/send.ts +43 -0
- package/src/messaging/providers/telegram-bot/transport.ts +25 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +30 -7
- package/src/notifications/adapters/telegram.ts +48 -1
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/conversation-seed-composer.ts +7 -2
- package/src/oauth/byo-connection.test.ts +63 -0
- package/src/oauth/byo-connection.ts +16 -15
- package/src/oauth/connection.test.ts +111 -0
- package/src/oauth/connection.ts +142 -1
- package/src/oauth/platform-connection.test.ts +34 -0
- package/src/oauth/platform-connection.ts +28 -5
- package/src/oauth/seed-providers.ts +15 -1
- package/src/permissions/types.ts +3 -1
- package/src/persistence/conversation-crud.ts +46 -0
- package/src/persistence/conversation-types.ts +11 -9
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/369-create-app-pins.ts +37 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/infrastructure.ts +17 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +6 -0
- package/src/plugins/mtime-cache.ts +11 -0
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/connection-resolution.ts +23 -1
- package/src/providers/content-blocks.ts +9 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +13 -5
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +0 -9
- package/src/providers/inference/credential-rotation.ts +61 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/model-catalog.ts +170 -125
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +42 -60
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/orphan-tool-result-guard.test.ts +34 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +75 -29
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/openrouter/client.ts +0 -1
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/providers/vercel-ai-gateway/client.ts +0 -1
- package/src/runtime/AGENTS.md +1 -0
- package/src/runtime/__tests__/desktop-presence.test.ts +27 -4
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/channel-readiness-service.ts +214 -14
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +2 -2
- package/src/runtime/desktop-presence.ts +24 -21
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-server.ts +181 -1
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +104 -0
- package/src/runtime/migrations/staged-import-path.ts +116 -0
- package/src/runtime/routes/__tests__/app-pin-routes.test.ts +383 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -56
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/app-management-routes.ts +140 -29
- package/src/runtime/routes/channel-availability-routes.ts +1 -0
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/guardian-approval-interception.ts +24 -33
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +2 -0
- package/src/runtime/routes/inbound-message-handler.ts +10 -7
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +166 -308
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +158 -335
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +24 -4
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/live-voice-routes.test.ts +40 -1
- package/src/runtime/routes/live-voice-routes.ts +34 -0
- package/src/runtime/routes/migration-routes.ts +218 -10
- package/src/runtime/routes/oauth-commands-routes.ts +23 -16
- package/src/runtime/routes/plugins-routes.ts +12 -28
- package/src/runtime/routes/question-routes.ts +6 -0
- package/src/runtime/routes/secret-routes.ts +7 -27
- package/src/runtime/routes/settings-routes.ts +9 -6
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +230 -121
- package/src/runtime/slack-reply-session.ts +113 -81
- package/src/runtime/{slack-task-progress.test.ts → task-progress.test.ts} +1 -28
- package/src/runtime/{slack-task-progress.ts → task-progress.ts} +30 -51
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +108 -0
- package/src/telemetry/live-voice-funnel.ts +75 -8
- package/src/tools/credentials/store.ts +18 -6
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +97 -2
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +141 -55
- package/src/tools/types.ts +2 -1
- package/src/util/oauth-request-body.test.ts +74 -0
- package/src/util/oauth-request-body.ts +60 -0
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
|
@@ -88,18 +88,29 @@ mock.module("../../gateway-client.js", () => ({
|
|
|
88
88
|
},
|
|
89
89
|
}));
|
|
90
90
|
|
|
91
|
-
const sentReactions: Array<{
|
|
92
|
-
callbackUrl: string;
|
|
93
|
-
target: Record<string, unknown>;
|
|
94
|
-
}> = [];
|
|
95
91
|
const sentStreamOps: Array<Record<string, unknown>> = [];
|
|
96
92
|
let sendChannelStreamOpImpl: (
|
|
97
93
|
op: Record<string, unknown>,
|
|
98
94
|
) => Promise<{ ok: boolean; ts?: string }> = async () => ({ ok: true });
|
|
99
|
-
const
|
|
95
|
+
const sentActivity: Array<Record<string, unknown>> = [];
|
|
96
|
+
let setChannelActivityImpl: (
|
|
97
|
+
target: Record<string, unknown>,
|
|
98
|
+
) => { ok: boolean } | Promise<{ ok: boolean }> = () => ({ ok: true });
|
|
99
|
+
// Undefined stands for a channel whose indicator holds until it is changed, so
|
|
100
|
+
// the controller reports each phase once and the assertions read as the turn's
|
|
101
|
+
// lifecycle rather than as timer ticks. A test that needs the self-expiring
|
|
102
|
+
// shape sets a cadence.
|
|
103
|
+
let activityRefreshMsImpl: () => number | undefined = () => undefined;
|
|
100
104
|
mock.module("../../../messaging/providers/index.js", () => ({
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
supportsChannelActivity: () => true,
|
|
106
|
+
channelActivityRefreshMs: () => activityRefreshMsImpl(),
|
|
107
|
+
setChannelActivity: async (
|
|
108
|
+
_callbackUrl: string,
|
|
109
|
+
target: Record<string, unknown>,
|
|
110
|
+
) => {
|
|
111
|
+
sentActivity.push(target);
|
|
112
|
+
return setChannelActivityImpl(target);
|
|
113
|
+
},
|
|
103
114
|
sendChannelStreamOp: async (
|
|
104
115
|
_callbackUrl: string,
|
|
105
116
|
_chatId: string,
|
|
@@ -108,20 +119,6 @@ mock.module("../../../messaging/providers/index.js", () => ({
|
|
|
108
119
|
sentStreamOps.push(op);
|
|
109
120
|
return sendChannelStreamOpImpl(op);
|
|
110
121
|
},
|
|
111
|
-
setChannelThreadStatus: async (
|
|
112
|
-
_callbackUrl: string,
|
|
113
|
-
status: Record<string, unknown>,
|
|
114
|
-
) => {
|
|
115
|
-
sentThreadStatuses.push(status);
|
|
116
|
-
return { ok: true };
|
|
117
|
-
},
|
|
118
|
-
sendChannelReaction: async (
|
|
119
|
-
callbackUrl: string,
|
|
120
|
-
target: Record<string, unknown>,
|
|
121
|
-
) => {
|
|
122
|
-
sentReactions.push({ callbackUrl, target });
|
|
123
|
-
return { ok: true };
|
|
124
|
-
},
|
|
125
122
|
}));
|
|
126
123
|
|
|
127
124
|
mock.module("../../channel-reply-delivery.js", () => ({
|
|
@@ -154,8 +151,8 @@ import type { MessageProcessor } from "../../http-types.js";
|
|
|
154
151
|
import {
|
|
155
152
|
isBoundGuardianActor,
|
|
156
153
|
processChannelMessageInBackground,
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
shouldShowActivityForText,
|
|
155
|
+
shouldShowActivityImmediately,
|
|
159
156
|
} from "./background-dispatch.js";
|
|
160
157
|
import { __resetChannelTurnAdmissionForTests } from "./channel-turn-admission.js";
|
|
161
158
|
|
|
@@ -163,10 +160,11 @@ beforeEach(() => {
|
|
|
163
160
|
__resetChannelTurnAdmissionForTests();
|
|
164
161
|
clearConversations();
|
|
165
162
|
deliveredChannelReplies.length = 0;
|
|
166
|
-
sentReactions.length = 0;
|
|
167
163
|
sentStreamOps.length = 0;
|
|
168
164
|
sendChannelStreamOpImpl = async () => ({ ok: true });
|
|
169
|
-
|
|
165
|
+
sentActivity.length = 0;
|
|
166
|
+
setChannelActivityImpl = () => ({ ok: true });
|
|
167
|
+
activityRefreshMsImpl = () => undefined;
|
|
170
168
|
markedProcessedEvents.length = 0;
|
|
171
169
|
processingFailureEvents.length = 0;
|
|
172
170
|
retryableFailureEvents.length = 0;
|
|
@@ -568,11 +566,11 @@ describe("processChannelMessageInBackground — reply delivery", () => {
|
|
|
568
566
|
expect(slackStreamOps()).toEqual([
|
|
569
567
|
{
|
|
570
568
|
action: "start",
|
|
571
|
-
threadTs,
|
|
572
|
-
|
|
573
|
-
|
|
569
|
+
anchorMessageId: threadTs,
|
|
570
|
+
text: "Streamed DM reply.",
|
|
571
|
+
appended: "Streamed DM reply.",
|
|
574
572
|
},
|
|
575
|
-
{ action: "stop", streamTs },
|
|
573
|
+
{ action: "stop", streamId: streamTs, text: "Streamed DM reply." },
|
|
576
574
|
]);
|
|
577
575
|
expect(
|
|
578
576
|
deliveredChannelReplies
|
|
@@ -893,13 +891,7 @@ describe("processChannelMessageInBackground — admission (queue if busy)", () =
|
|
|
893
891
|
});
|
|
894
892
|
});
|
|
895
893
|
|
|
896
|
-
describe("
|
|
897
|
-
const slackStatusLabels = [
|
|
898
|
-
"is on it",
|
|
899
|
-
"is working hard",
|
|
900
|
-
"is touching grass",
|
|
901
|
-
];
|
|
902
|
-
|
|
894
|
+
describe("channel activity timing", () => {
|
|
903
895
|
const trustCtx: TrustContext = {
|
|
904
896
|
trustClass: "guardian",
|
|
905
897
|
guardianExternalUserId: "guardian-1",
|
|
@@ -909,73 +901,74 @@ describe("Slack thinking status timing", () => {
|
|
|
909
901
|
const flush = (): Promise<void> =>
|
|
910
902
|
new Promise((resolve) => setTimeout(resolve, 10));
|
|
911
903
|
|
|
904
|
+
const phases = (): unknown[] =>
|
|
905
|
+
sentActivity.map((entry) => (entry as { phase?: unknown }).phase);
|
|
906
|
+
|
|
912
907
|
beforeEach(() => {
|
|
913
908
|
deliveredChannelReplies.length = 0;
|
|
914
909
|
});
|
|
915
910
|
|
|
916
|
-
test("recognizes only deliverable text as a
|
|
917
|
-
expect(
|
|
918
|
-
expect(
|
|
919
|
-
expect(
|
|
920
|
-
expect(
|
|
921
|
-
expect(
|
|
922
|
-
expect(
|
|
911
|
+
test("recognizes only deliverable text as a reason to show activity", () => {
|
|
912
|
+
expect(shouldShowActivityForText("")).toBe(false);
|
|
913
|
+
expect(shouldShowActivityForText(" ")).toBe(false);
|
|
914
|
+
expect(shouldShowActivityForText("<")).toBe(false);
|
|
915
|
+
expect(shouldShowActivityForText("<no_response")).toBe(false);
|
|
916
|
+
expect(shouldShowActivityForText("<no_response/>")).toBe(false);
|
|
917
|
+
expect(shouldShowActivityForText(" <no_response /> ")).toBe(false);
|
|
918
|
+
expect(shouldShowActivityForText("Real response.")).toBe(true);
|
|
919
|
+
expect(shouldShowActivityForText("<no_response/>\nReal response.")).toBe(
|
|
920
|
+
true,
|
|
921
|
+
);
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
test("recognizes a direct conversation in every channel's word for one", () => {
|
|
925
|
+
// One idea, three spellings, because `chatType` arrives as whatever the
|
|
926
|
+
// channel called it. Slack `im`, Discord `dm`, Telegram `private`. Missing
|
|
927
|
+
// one does not fail loudly: that channel silently reads as an ambient room
|
|
928
|
+
// and stops showing an indicator it used to show.
|
|
929
|
+
expect(shouldShowActivityImmediately({ chatType: "im" })).toBe(true);
|
|
930
|
+
expect(shouldShowActivityImmediately({ chatType: "dm" })).toBe(true);
|
|
931
|
+
expect(shouldShowActivityImmediately({ chatType: "private" })).toBe(true);
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
test("treats a room with other readers as ambient, including a group DM", () => {
|
|
935
|
+
// Slack's `mpim` has other readers, so it is a room: the assistant may
|
|
936
|
+
// process the traffic and decide to stay quiet, and an indicator there
|
|
937
|
+
// announces it was reading.
|
|
938
|
+
expect(shouldShowActivityImmediately({ chatType: "mpim" })).toBe(false);
|
|
939
|
+
expect(shouldShowActivityImmediately({ chatType: "channel" })).toBe(false);
|
|
940
|
+
expect(shouldShowActivityImmediately({ chatType: "supergroup" })).toBe(
|
|
923
941
|
false,
|
|
924
942
|
);
|
|
925
|
-
expect(
|
|
926
|
-
expect(
|
|
927
|
-
shouldStartSlackThinkingStatusForText("<no_response/>\nReal response."),
|
|
928
|
-
).toBe(true);
|
|
943
|
+
expect(shouldShowActivityImmediately({})).toBe(false);
|
|
929
944
|
});
|
|
930
945
|
|
|
931
|
-
test("
|
|
932
|
-
expect(
|
|
933
|
-
shouldStartSlackThinkingStatusImmediately({
|
|
934
|
-
sourceChannel: "slack",
|
|
935
|
-
chatType: "im",
|
|
936
|
-
}),
|
|
937
|
-
).toBe(true);
|
|
938
|
-
expect(
|
|
939
|
-
shouldStartSlackThinkingStatusImmediately({
|
|
940
|
-
sourceChannel: "slack",
|
|
941
|
-
slackBotMentioned: true,
|
|
942
|
-
}),
|
|
943
|
-
).toBe(true);
|
|
946
|
+
test("shows activity immediately when the assistant is mentioned", () => {
|
|
947
|
+
expect(shouldShowActivityImmediately({ botMentioned: true })).toBe(true);
|
|
944
948
|
expect(
|
|
945
|
-
|
|
946
|
-
sourceChannel: "slack",
|
|
949
|
+
shouldShowActivityImmediately({
|
|
947
950
|
chatType: "channel",
|
|
951
|
+
botMentioned: true,
|
|
948
952
|
}),
|
|
949
|
-
).toBe(
|
|
950
|
-
expect(
|
|
951
|
-
shouldStartSlackThinkingStatusImmediately({
|
|
952
|
-
sourceChannel: "telegram",
|
|
953
|
-
chatType: "im",
|
|
954
|
-
slackBotMentioned: true,
|
|
955
|
-
}),
|
|
956
|
-
).toBe(false);
|
|
953
|
+
).toBe(true);
|
|
957
954
|
});
|
|
958
955
|
|
|
959
|
-
test("
|
|
960
|
-
const conversationId = "conv-dm-immediate-
|
|
956
|
+
test("shows activity immediately in a DM and settles it when the turn ends", async () => {
|
|
957
|
+
const conversationId = "conv-dm-immediate-activity";
|
|
961
958
|
const channelId = "D-DM-IMMEDIATE";
|
|
962
959
|
const messageTs = "1700000000.000010";
|
|
963
960
|
|
|
964
961
|
const processMessage: MessageProcessor = async () => {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
messageId: messageTs,
|
|
969
|
-
emoji: "eyes",
|
|
970
|
-
action: "add",
|
|
971
|
-
});
|
|
962
|
+
// Already showing before the turn does any work: a DM is addressed to
|
|
963
|
+
// the assistant, so nothing needs to be observed first.
|
|
964
|
+
expect(phases()).toEqual(["thinking"]);
|
|
972
965
|
return { messageId: "user-msg-dm-immediate" };
|
|
973
966
|
};
|
|
974
967
|
|
|
975
968
|
processChannelMessageInBackground({
|
|
976
969
|
processMessage,
|
|
977
970
|
conversationId,
|
|
978
|
-
eventId: "evt-dm-immediate-
|
|
971
|
+
eventId: "evt-dm-immediate-activity",
|
|
979
972
|
content: "dm message",
|
|
980
973
|
sourceChannel: "slack",
|
|
981
974
|
sourceInterface: "slack",
|
|
@@ -988,39 +981,27 @@ describe("Slack thinking status timing", () => {
|
|
|
988
981
|
|
|
989
982
|
await flush();
|
|
990
983
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
emoji: "eyes",
|
|
997
|
-
action: "remove",
|
|
998
|
-
},
|
|
999
|
-
]);
|
|
984
|
+
// `idle` is owed explicitly. A channel that holds its indicator keeps it
|
|
985
|
+
// up until told otherwise, so a missing terminal phase is a stuck spinner
|
|
986
|
+
// rather than a cosmetic slip.
|
|
987
|
+
expect(phases()).toEqual(["thinking", "idle"]);
|
|
988
|
+
expect(sentActivity[0]).toEqual({ chatId: channelId, phase: "thinking" });
|
|
1000
989
|
});
|
|
1001
990
|
|
|
1002
|
-
test("
|
|
1003
|
-
const conversationId = "conv-mention-immediate-
|
|
991
|
+
test("shows activity immediately for an app mention and settles it", async () => {
|
|
992
|
+
const conversationId = "conv-mention-immediate-activity";
|
|
1004
993
|
const channelId = "C-MENTION-IMMEDIATE";
|
|
1005
994
|
const threadTs = "1700000000.000011";
|
|
1006
995
|
|
|
1007
996
|
const processMessage: MessageProcessor = async () => {
|
|
1008
|
-
expect(
|
|
1009
|
-
expect(sentThreadStatuses[0]).toEqual({
|
|
1010
|
-
chatId: channelId,
|
|
1011
|
-
threadTs,
|
|
1012
|
-
status: expect.any(String),
|
|
1013
|
-
loadingMessages: ["Thinking\u2026"],
|
|
1014
|
-
});
|
|
1015
|
-
const threadStatus = sentThreadStatuses[0] as { status: string };
|
|
1016
|
-
expect(slackStatusLabels).toContain(threadStatus.status);
|
|
997
|
+
expect(phases()).toEqual(["thinking"]);
|
|
1017
998
|
return { messageId: "user-msg-mention-immediate" };
|
|
1018
999
|
};
|
|
1019
1000
|
|
|
1020
1001
|
processChannelMessageInBackground({
|
|
1021
1002
|
processMessage,
|
|
1022
1003
|
conversationId,
|
|
1023
|
-
eventId: "evt-mention-immediate-
|
|
1004
|
+
eventId: "evt-mention-immediate-activity",
|
|
1024
1005
|
content: "@assistant please respond",
|
|
1025
1006
|
sourceChannel: "slack",
|
|
1026
1007
|
sourceInterface: "slack",
|
|
@@ -1033,204 +1014,123 @@ describe("Slack thinking status timing", () => {
|
|
|
1033
1014
|
|
|
1034
1015
|
await flush();
|
|
1035
1016
|
|
|
1036
|
-
|
|
1037
|
-
(entry) => (entry as { status?: string }).status,
|
|
1038
|
-
);
|
|
1039
|
-
expect(slackStatusLabels).toContain(statuses[0]!);
|
|
1040
|
-
expect(statuses[1]).toBe("");
|
|
1017
|
+
expect(phases()).toEqual(["thinking", "idle"]);
|
|
1041
1018
|
});
|
|
1042
1019
|
|
|
1043
|
-
test("
|
|
1044
|
-
const conversationId = "conv-
|
|
1045
|
-
const channelId = "
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
options?.onEvent?.({
|
|
1054
|
-
type: "assistant_text_delta",
|
|
1055
|
-
text: "<no_response/>",
|
|
1056
|
-
conversationId,
|
|
1057
|
-
});
|
|
1058
|
-
return { messageId: "user-msg-no-response" };
|
|
1020
|
+
test("retries a terminal transition the channel failed to accept", async () => {
|
|
1021
|
+
const conversationId = "conv-idle-retry";
|
|
1022
|
+
const channelId = "D-IDLE-RETRY";
|
|
1023
|
+
let idleAttempts = 0;
|
|
1024
|
+
setChannelActivityImpl = (target) => {
|
|
1025
|
+
if (target.phase === "idle") {
|
|
1026
|
+
idleAttempts += 1;
|
|
1027
|
+
return { ok: idleAttempts > 1 };
|
|
1028
|
+
}
|
|
1029
|
+
return { ok: true };
|
|
1059
1030
|
};
|
|
1060
1031
|
|
|
1061
1032
|
processChannelMessageInBackground({
|
|
1062
|
-
processMessage,
|
|
1033
|
+
processMessage: async () => ({ messageId: "user-msg-idle-retry" }),
|
|
1063
1034
|
conversationId,
|
|
1064
|
-
eventId: "evt-
|
|
1065
|
-
content: "
|
|
1035
|
+
eventId: "evt-idle-retry",
|
|
1036
|
+
content: "dm message",
|
|
1066
1037
|
sourceChannel: "slack",
|
|
1067
1038
|
sourceInterface: "slack",
|
|
1068
1039
|
externalChatId: channelId,
|
|
1069
1040
|
trustCtx,
|
|
1070
1041
|
metadataHints: [],
|
|
1071
|
-
|
|
1042
|
+
chatType: "im",
|
|
1043
|
+
replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&messageTs=1700000000.000020`,
|
|
1072
1044
|
});
|
|
1073
1045
|
|
|
1074
1046
|
await flush();
|
|
1075
1047
|
|
|
1076
|
-
|
|
1048
|
+
// A dropped `idle` is not cosmetic on a channel that holds its indicator:
|
|
1049
|
+
// Slack keeps showing the assistant as working for an hour, so the
|
|
1050
|
+
// terminal transition is the one worth attempting twice.
|
|
1051
|
+
expect(idleAttempts).toBe(2);
|
|
1077
1052
|
});
|
|
1078
1053
|
|
|
1079
|
-
test("
|
|
1080
|
-
const conversationId = "conv-
|
|
1081
|
-
const channelId = "
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
text: "b>Working on it.",
|
|
1099
|
-
conversationId,
|
|
1100
|
-
});
|
|
1101
|
-
return { messageId: "user-msg-real-response" };
|
|
1054
|
+
test("runs idle next when the turn stops during a slow request, with no stale busy phase between", async () => {
|
|
1055
|
+
const conversationId = "conv-slow-refresh";
|
|
1056
|
+
const channelId = "D-SLOW-REFRESH";
|
|
1057
|
+
|
|
1058
|
+
// A self-expiring channel re-asserts on a timer, which is the only shape
|
|
1059
|
+
// where a refresh backlog can form at all.
|
|
1060
|
+
activityRefreshMsImpl = () => 5;
|
|
1061
|
+
|
|
1062
|
+
let release: (() => void) | undefined;
|
|
1063
|
+
let calls = 0;
|
|
1064
|
+
setChannelActivityImpl = (target) => {
|
|
1065
|
+
calls += 1;
|
|
1066
|
+
if (calls === 1 && target.phase !== "idle") {
|
|
1067
|
+
// Hold the first busy call open across many refresh intervals.
|
|
1068
|
+
return new Promise<{ ok: boolean }>((resolve) => {
|
|
1069
|
+
release = () => resolve({ ok: true });
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
return { ok: true };
|
|
1102
1073
|
};
|
|
1103
1074
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
content: "please respond",
|
|
1109
|
-
sourceChannel: "slack",
|
|
1110
|
-
sourceInterface: "slack",
|
|
1111
|
-
externalChatId: channelId,
|
|
1112
|
-
trustCtx,
|
|
1113
|
-
metadataHints: [],
|
|
1114
|
-
replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&threadTs=${threadTs}`,
|
|
1115
|
-
});
|
|
1116
|
-
|
|
1117
|
-
await flush();
|
|
1118
|
-
|
|
1119
|
-
const statuses = sentThreadStatuses.map(
|
|
1120
|
-
(entry) => (entry as { status?: string }).status,
|
|
1121
|
-
);
|
|
1122
|
-
expect(slackStatusLabels).toContain(statuses[0]!);
|
|
1123
|
-
expect(statuses[1]).toBe("");
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
test("buffers task_progress for ambiguous Slack turns until deliverable text appears", async () => {
|
|
1127
|
-
const conversationId = "conv-progress-buffered";
|
|
1128
|
-
const channelId = "C-PROGRESS-BUFFERED";
|
|
1129
|
-
const threadTs = "1700000000.000012";
|
|
1130
|
-
|
|
1131
|
-
const processMessage: MessageProcessor = async (
|
|
1132
|
-
_conversationId,
|
|
1133
|
-
_content,
|
|
1134
|
-
options,
|
|
1135
|
-
) => {
|
|
1136
|
-
options?.onEvent?.({
|
|
1137
|
-
type: "ui_surface_show",
|
|
1138
|
-
conversationId,
|
|
1139
|
-
surfaceId: "surface-progress",
|
|
1140
|
-
surfaceType: "card",
|
|
1141
|
-
data: {
|
|
1142
|
-
title: "Task progress",
|
|
1143
|
-
body: "Working",
|
|
1144
|
-
template: "task_progress",
|
|
1145
|
-
templateData: {
|
|
1146
|
-
steps: [
|
|
1147
|
-
{ label: "Search docs", status: "in_progress" },
|
|
1148
|
-
{ label: "Summarize", status: "pending" },
|
|
1149
|
-
],
|
|
1150
|
-
},
|
|
1151
|
-
},
|
|
1152
|
-
});
|
|
1153
|
-
expect(deliveredChannelReplies).toEqual([]);
|
|
1154
|
-
|
|
1155
|
-
options?.onEvent?.({
|
|
1156
|
-
type: "assistant_text_delta",
|
|
1157
|
-
text: "I found the answer.",
|
|
1158
|
-
conversationId,
|
|
1159
|
-
});
|
|
1160
|
-
return { messageId: "user-msg-progress-buffered" };
|
|
1075
|
+
const processMessage: MessageProcessor = async () => {
|
|
1076
|
+
// Long enough for several ticks to fire while the first call is unresolved.
|
|
1077
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
1078
|
+
return { messageId: "user-msg-slow-refresh" };
|
|
1161
1079
|
};
|
|
1162
1080
|
|
|
1163
1081
|
processChannelMessageInBackground({
|
|
1164
1082
|
processMessage,
|
|
1165
1083
|
conversationId,
|
|
1166
|
-
eventId: "evt-
|
|
1167
|
-
content: "
|
|
1084
|
+
eventId: "evt-slow-refresh",
|
|
1085
|
+
content: "dm message",
|
|
1168
1086
|
sourceChannel: "slack",
|
|
1169
1087
|
sourceInterface: "slack",
|
|
1170
1088
|
externalChatId: channelId,
|
|
1171
1089
|
trustCtx,
|
|
1172
1090
|
metadataHints: [],
|
|
1173
|
-
|
|
1091
|
+
chatType: "im",
|
|
1092
|
+
replyCallbackUrl: `https://example.test/deliver/slack?channel=${channelId}&messageTs=1700000000.000030`,
|
|
1174
1093
|
});
|
|
1175
1094
|
|
|
1095
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
1096
|
+
release?.();
|
|
1176
1097
|
await flush();
|
|
1177
1098
|
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
threadTs,
|
|
1189
|
-
status: "",
|
|
1190
|
-
},
|
|
1191
|
-
]);
|
|
1192
|
-
expect(slackStatusLabels).toContain(
|
|
1193
|
-
(statuses[0] as { status: string }).status,
|
|
1194
|
-
);
|
|
1099
|
+
const seen = phases();
|
|
1100
|
+
// The terminal phase is last and nothing busy follows it. A busy phase
|
|
1101
|
+
// executing after `idle` is the visible bug: the indicator comes back on a
|
|
1102
|
+
// turn that has already replied, which teaches people to distrust it.
|
|
1103
|
+
expect(seen.at(-1)).toBe("idle");
|
|
1104
|
+
expect(seen.slice(seen.indexOf("idle") + 1)).toEqual([]);
|
|
1105
|
+
// Ticks did not accumulate behind the outstanding call. Counting rather
|
|
1106
|
+
// than checking the last entry is what catches a backlog that happens to
|
|
1107
|
+
// drain in a harmless order.
|
|
1108
|
+
expect(seen.filter((phase) => phase === "thinking")).toHaveLength(1);
|
|
1195
1109
|
});
|
|
1196
1110
|
|
|
1197
|
-
test("
|
|
1198
|
-
const conversationId = "conv-
|
|
1199
|
-
const channelId = "C-
|
|
1200
|
-
const threadTs = "1700000000.
|
|
1111
|
+
test("stays silent for a turn that decides not to answer", async () => {
|
|
1112
|
+
const conversationId = "conv-no-response-activity";
|
|
1113
|
+
const channelId = "C-NO-RESPONSE";
|
|
1114
|
+
const threadTs = "1700000000.000003";
|
|
1201
1115
|
|
|
1202
1116
|
const processMessage: MessageProcessor = async (
|
|
1203
1117
|
_conversationId,
|
|
1204
1118
|
_content,
|
|
1205
1119
|
options,
|
|
1206
1120
|
) => {
|
|
1207
|
-
options?.onEvent?.({
|
|
1208
|
-
type: "ui_surface_show",
|
|
1209
|
-
conversationId,
|
|
1210
|
-
surfaceId: "surface-progress-no-response",
|
|
1211
|
-
surfaceType: "card",
|
|
1212
|
-
data: {
|
|
1213
|
-
title: "Task progress",
|
|
1214
|
-
body: "Working",
|
|
1215
|
-
template: "task_progress",
|
|
1216
|
-
templateData: {
|
|
1217
|
-
steps: [{ label: "Inspect", status: "in_progress" }],
|
|
1218
|
-
},
|
|
1219
|
-
},
|
|
1220
|
-
});
|
|
1221
1121
|
options?.onEvent?.({
|
|
1222
1122
|
type: "assistant_text_delta",
|
|
1223
1123
|
text: "<no_response/>",
|
|
1224
1124
|
conversationId,
|
|
1225
1125
|
});
|
|
1226
|
-
return { messageId: "user-msg-
|
|
1126
|
+
return { messageId: "user-msg-no-response" };
|
|
1227
1127
|
};
|
|
1228
1128
|
|
|
1229
1129
|
processChannelMessageInBackground({
|
|
1230
1130
|
processMessage,
|
|
1231
1131
|
conversationId,
|
|
1232
|
-
eventId: "evt-
|
|
1233
|
-
content: "ambient chatter",
|
|
1132
|
+
eventId: "evt-no-response-activity",
|
|
1133
|
+
content: "ambient channel chatter",
|
|
1234
1134
|
sourceChannel: "slack",
|
|
1235
1135
|
sourceInterface: "slack",
|
|
1236
1136
|
externalChatId: channelId,
|
|
@@ -1241,61 +1141,44 @@ describe("Slack thinking status timing", () => {
|
|
|
1241
1141
|
|
|
1242
1142
|
await flush();
|
|
1243
1143
|
|
|
1144
|
+
// Not merely "no spinner": nothing at all was said to the channel. An
|
|
1145
|
+
// `idle` here would still be a message about a turn nobody asked to see,
|
|
1146
|
+
// and in a shared room it announces that the assistant read the traffic.
|
|
1147
|
+
expect(sentActivity).toEqual([]);
|
|
1244
1148
|
expect(deliveredChannelReplies).toEqual([]);
|
|
1245
1149
|
});
|
|
1246
1150
|
|
|
1247
|
-
test("
|
|
1248
|
-
const conversationId = "conv-
|
|
1249
|
-
const channelId = "C-
|
|
1250
|
-
const threadTs = "1700000000.
|
|
1151
|
+
test("waits for real text in a room it was not addressed in, then settles", async () => {
|
|
1152
|
+
const conversationId = "conv-real-response-activity";
|
|
1153
|
+
const channelId = "C-REAL-RESPONSE";
|
|
1154
|
+
const threadTs = "1700000000.000004";
|
|
1251
1155
|
|
|
1252
1156
|
const processMessage: MessageProcessor = async (
|
|
1253
1157
|
_conversationId,
|
|
1254
1158
|
_content,
|
|
1255
1159
|
options,
|
|
1256
1160
|
) => {
|
|
1257
|
-
options?.onEvent?.({
|
|
1258
|
-
type: "ui_surface_show",
|
|
1259
|
-
conversationId,
|
|
1260
|
-
surfaceId: "surface-progress-update",
|
|
1261
|
-
surfaceType: "card",
|
|
1262
|
-
data: {
|
|
1263
|
-
title: "Task progress",
|
|
1264
|
-
body: "Working",
|
|
1265
|
-
template: "task_progress",
|
|
1266
|
-
templateData: {
|
|
1267
|
-
steps: [
|
|
1268
|
-
{ label: "Read request", status: "in_progress" },
|
|
1269
|
-
{ label: "Write answer", status: "pending" },
|
|
1270
|
-
],
|
|
1271
|
-
},
|
|
1272
|
-
},
|
|
1273
|
-
});
|
|
1274
1161
|
options?.onEvent?.({
|
|
1275
1162
|
type: "assistant_text_delta",
|
|
1276
|
-
text: "
|
|
1163
|
+
text: "<",
|
|
1277
1164
|
conversationId,
|
|
1278
1165
|
});
|
|
1166
|
+
// A partial `<` could still become `<no_response/>`, so nothing shows.
|
|
1167
|
+
expect(sentActivity).toEqual([]);
|
|
1168
|
+
|
|
1279
1169
|
options?.onEvent?.({
|
|
1280
|
-
type: "
|
|
1170
|
+
type: "assistant_text_delta",
|
|
1171
|
+
text: "b>Working on it.",
|
|
1281
1172
|
conversationId,
|
|
1282
|
-
surfaceId: "surface-progress-update",
|
|
1283
|
-
data: {
|
|
1284
|
-
templateData: {
|
|
1285
|
-
steps: [
|
|
1286
|
-
{ label: "Read request", status: "completed" },
|
|
1287
|
-
{ label: "Write answer", status: "in_progress" },
|
|
1288
|
-
],
|
|
1289
|
-
},
|
|
1290
|
-
},
|
|
1291
1173
|
});
|
|
1292
|
-
|
|
1174
|
+
expect(phases()).toEqual(["thinking"]);
|
|
1175
|
+
return { messageId: "user-msg-real-response" };
|
|
1293
1176
|
};
|
|
1294
1177
|
|
|
1295
1178
|
processChannelMessageInBackground({
|
|
1296
1179
|
processMessage,
|
|
1297
1180
|
conversationId,
|
|
1298
|
-
eventId: "evt-
|
|
1181
|
+
eventId: "evt-real-response-activity",
|
|
1299
1182
|
content: "please respond",
|
|
1300
1183
|
sourceChannel: "slack",
|
|
1301
1184
|
sourceInterface: "slack",
|
|
@@ -1307,31 +1190,6 @@ describe("Slack thinking status timing", () => {
|
|
|
1307
1190
|
|
|
1308
1191
|
await flush();
|
|
1309
1192
|
|
|
1310
|
-
|
|
1311
|
-
expect(statuses).toEqual([
|
|
1312
|
-
{
|
|
1313
|
-
chatId: channelId,
|
|
1314
|
-
threadTs,
|
|
1315
|
-
status: expect.any(String),
|
|
1316
|
-
loadingMessages: ["In progress (1/2): Read request"],
|
|
1317
|
-
},
|
|
1318
|
-
{
|
|
1319
|
-
chatId: channelId,
|
|
1320
|
-
threadTs,
|
|
1321
|
-
status: expect.any(String),
|
|
1322
|
-
loadingMessages: ["In progress (2/2): Write answer"],
|
|
1323
|
-
},
|
|
1324
|
-
{
|
|
1325
|
-
chatId: channelId,
|
|
1326
|
-
threadTs,
|
|
1327
|
-
status: "",
|
|
1328
|
-
},
|
|
1329
|
-
]);
|
|
1330
|
-
expect(slackStatusLabels).toContain(
|
|
1331
|
-
(statuses[0] as { status: string }).status,
|
|
1332
|
-
);
|
|
1333
|
-
expect(slackStatusLabels).toContain(
|
|
1334
|
-
(statuses[1] as { status: string }).status,
|
|
1335
|
-
);
|
|
1193
|
+
expect(phases()).toEqual(["thinking", "idle"]);
|
|
1336
1194
|
});
|
|
1337
1195
|
});
|